26  Lab: Zigbee Mesh Network

Interactive ESP32 simulation demonstrating Zigbee mesh networking concepts

Lab execution time can be estimated before starting runs:

\[ T_{\text{total}} = N_{\text{runs}} \times (t_{\text{setup}} + t_{\text{run}} + t_{\text{review}}) \]

Worked example: With 5 runs and per-run times of 4 min setup, 6 min execution, and 3 min review, total lab time is \(5\times(4+6+3)=65\) minutes. This prevents under-scoping and helps schedule complete experimental cycles.

In 60 Seconds

This hands-on lab uses an ESP32 Wokwi simulation to demonstrate Zigbee mesh networking concepts including peer-to-peer communication via ESP-NOW, mesh routing with hop counting, and self-healing when nodes fail. You will build a simulated sensor network, observe multi-hop message delivery, and experiment with node failures to see how the mesh automatically reroutes traffic. No physical hardware required.

26.1 Learning Objectives

By completing this lab, you will be able to:

  • Configure peer-to-peer communication using ESP-NOW across multiple ESP32 nodes
  • Construct message relay and forwarding logic to simulate multi-hop mesh routing
  • Deploy a four-node mesh topology with coordinator, router, and end device roles
  • Analyse self-healing behaviour by disabling routers and measuring reroute latency
  • Diagnose wireless communication failures by interpreting serial monitor message flow

In this lab, you will build a simulated Zigbee mesh network using ESP32 microcontrollers. You will create coordinator, router, and end device nodes, then watch them form a network and exchange messages. It is like watching a team organize itself – each device finds its role and starts cooperating automatically.

26.2 Introduction

This interactive lab uses the Wokwi ESP32 simulator to demonstrate mesh networking concepts that underpin Zigbee. Since Wokwi does not have native Zigbee support, we use ESP-NOW, a peer-to-peer protocol that mimics many mesh networking behaviors.

Why ESP-NOW for Learning Zigbee?

ESP-NOW and Zigbee share key characteristics:

Feature Zigbee ESP-NOW
Frequency 2.4 GHz 2.4 GHz
Range 10-100m 10-250m
Low power Yes Yes
Peer-to-peer Yes Yes
Mesh capable Native Manual relay
Max peers 65,000+ 20 per device

While Zigbee has sophisticated built-in mesh routing (AODV), ESP-NOW requires manual message relay - which is actually better for learning because you implement the routing logic yourself!

26.3 Lab Components

The simulation contains four ESP32 nodes representing a Zigbee mesh network:

Node Role LED Behaviour Function
ESP32 #1 Coordinator STATUS_LED stays ON Network controller, message destination
ESP32 #2 Router A STATUS_LED blinks every 2 s Primary relay path
ESP32 #3 Router B STATUS_LED blinks every 2 s Backup relay path (self-healing)
ESP32 #4 End Device MSG_LED flashes on send Sensor node, message source

26.4 Embedded Wokwi Mesh Simulator

How to Use This Simulator
  1. Click “Start Simulation” to begin
  2. Watch the Serial Monitor for message flow logs
  3. LEDs blink when routing messages
  4. To test self-healing: Stop Router A by clicking the pause/stop button in its tab
  5. The network will automatically reroute through Router B
Simulator Setup Instructions

Since Wokwi doesn’t persist embedded projects, copy the complete mesh network code below into the simulator. The code creates a 4-node mesh network demonstrating all key Zigbee concepts.

26.5 Complete Mesh Network Code

Copy this code into each of 4 Wokwi tabs (change NODE_ROLE for each):

// ============================================================
// ESP-NOW Zigbee-Style Mesh Network Simulator
// Demonstrates: Coordinator, Router, End Device roles
//              Multi-hop routing, Self-healing, Broadcast/Unicast
// ============================================================

#include <WiFi.h>
#include <esp_now.h>

// ============ CONFIGURATION - CHANGE FOR EACH NODE ============
// Set ONE of these to 1, others to 0:
#define IS_COORDINATOR 0  // Node 1: Set to 1
#define IS_ROUTER_A    0  // Node 2: Set to 1
#define IS_ROUTER_B    0  // Node 3: Set to 1
#define IS_END_DEVICE  1  // Node 4: Set to 1 (default)

// LED Pin Configuration
#define STATUS_LED 2       // Built-in LED
#define MSG_LED 4          // Message activity LED

// ============ NETWORK CONSTANTS ============
#define MAX_HOPS 5
#define HEARTBEAT_INTERVAL 5000
#define ROUTE_TIMEOUT 15000
#define MAX_NEIGHBORS 10
#define MSG_RETRY_COUNT 3
#define ACK_TIMEOUT 500

// Broadcast address
uint8_t broadcastMAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// ============ MESSAGE STRUCTURES ============
enum MessageType {
  MSG_DATA = 0,
  MSG_HEARTBEAT = 1,
  MSG_ROUTE_REQUEST = 2,
  MSG_ROUTE_REPLY = 3,
  MSG_ACK = 4,
  MSG_BROADCAST = 5
};

typedef struct __attribute__((packed)) {
  uint8_t msgType;
  uint8_t srcMAC[6];
  uint8_t dstMAC[6];
  uint8_t hopCount;
  uint8_t maxHops;
  uint32_t seqNum;
  uint8_t payload[64];
  uint8_t payloadLen;
} MeshMessage;

typedef struct {
  uint8_t mac[6];
  char role[12];
  int8_t rssi;
  uint32_t lastSeen;
  bool isActive;
  bool canRoute;
} Neighbor;

// ============ GLOBAL STATE ============
Neighbor neighbors[MAX_NEIGHBORS];
int neighborCount = 0;
uint32_t messageSeq = 0;
uint32_t lastHeartbeat = 0;
uint32_t messagesRouted = 0;
uint32_t messagesReceived = 0;
uint32_t lastDataSend = 0;

char myRole[12];
uint8_t myMAC[6];

bool awaitingAck = false;
uint32_t ackSeqNum = 0;
uint32_t ackSentTime = 0;
int ackRetries = 0;

// ============ LED FUNCTIONS ============
void blinkLED(int pin, int times, int delayMs) {
  for (int i = 0; i < times; i++) {
    digitalWrite(pin, HIGH);
    delay(delayMs);
    digitalWrite(pin, LOW);
    delay(delayMs);
  }
}

void flashMessageLED() {
  digitalWrite(MSG_LED, HIGH);
  delay(50);
  digitalWrite(MSG_LED, LOW);
}

// ============ NEIGHBOR TABLE ============
int findNeighbor(const uint8_t* mac) {
  for (int i = 0; i < neighborCount; i++) {
    if (memcmp(neighbors[i].mac, mac, 6) == 0) return i;
  }
  return -1;
}

void updateNeighbor(const uint8_t* mac, const char* role, int8_t rssi) {
  int idx = findNeighbor(mac);

  if (idx >= 0) {
    neighbors[idx].rssi = rssi;
    neighbors[idx].lastSeen = millis();
    neighbors[idx].isActive = true;
    strncpy(neighbors[idx].role, role, 11);
    neighbors[idx].canRoute = (strcmp(role, "ROUTER") == 0 ||
                                strcmp(role, "COORDINATOR") == 0);
  } else if (neighborCount < MAX_NEIGHBORS) {
    memcpy(neighbors[neighborCount].mac, mac, 6);
    strncpy(neighbors[neighborCount].role, role, 11);
    neighbors[neighborCount].rssi = rssi;
    neighbors[neighborCount].lastSeen = millis();
    neighbors[neighborCount].isActive = true;
    neighbors[neighborCount].canRoute = (strcmp(role, "ROUTER") == 0 ||
                                          strcmp(role, "COORDINATOR") == 0);
    neighborCount++;
    Serial.printf("[MESH] New neighbor: %02X:%02X:%02X:%02X:%02X:%02X (%s)\n",
                  mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], role);
  }
}

void checkNeighborTimeouts() {
  for (int i = 0; i < neighborCount; i++) {
    if (neighbors[i].isActive &&
        (millis() - neighbors[i].lastSeen > ROUTE_TIMEOUT)) {
      neighbors[i].isActive = false;
      Serial.printf("[MESH] Neighbor timeout: %02X:%02X:%02X:%02X:%02X:%02X\n",
                    neighbors[i].mac[0], neighbors[i].mac[1], neighbors[i].mac[2],
                    neighbors[i].mac[3], neighbors[i].mac[4], neighbors[i].mac[5]);
      Serial.println("[MESH] >>> SELF-HEALING: Route may need update <<<");
    }
  }
}

void printNeighborTable() {
  Serial.println("\n========== NEIGHBOR TABLE ==========");
  for (int i = 0; i < neighborCount; i++) {
    Serial.printf("  %d. %02X:%02X:%02X:%02X:%02X:%02X - %s (RSSI: %d) %s\n",
                  i + 1, neighbors[i].mac[0], neighbors[i].mac[1],
                  neighbors[i].mac[2], neighbors[i].mac[3],
                  neighbors[i].mac[4], neighbors[i].mac[5],
                  neighbors[i].role, neighbors[i].rssi,
                  neighbors[i].isActive ? "[ACTIVE]" : "[OFFLINE]");
  }
  Serial.println("=====================================\n");
}

// ============ ROUTING ============
int findBestRoute(const uint8_t* destMAC) {
  int directIdx = findNeighbor(destMAC);
  if (directIdx >= 0 && neighbors[directIdx].isActive) return directIdx;

  int bestRouter = -1;
  int8_t bestRSSI = -127;

  for (int i = 0; i < neighborCount; i++) {
    if (neighbors[i].isActive && neighbors[i].canRoute) {
      if (neighbors[i].rssi > bestRSSI) {
        bestRSSI = neighbors[i].rssi;
        bestRouter = i;
      }
    }
  }

  if (bestRouter >= 0) {
    Serial.printf("[ROUTE] Selected: %02X:%02X:%02X:%02X:%02X:%02X (RSSI: %d)\n",
                  neighbors[bestRouter].mac[0], neighbors[bestRouter].mac[1],
                  neighbors[bestRouter].mac[2], neighbors[bestRouter].mac[3],
                  neighbors[bestRouter].mac[4], neighbors[bestRouter].mac[5],
                  bestRSSI);
  }
  return bestRouter;
}

// ============ MESSAGE SENDING ============
void sendMessage(MeshMessage* msg, const uint8_t* nextHop) {
  esp_err_t result = esp_now_send(nextHop, (uint8_t*)msg, sizeof(MeshMessage));
  flashMessageLED();

  if (result == ESP_OK) {
    Serial.printf("[TX] Sent %s (seq: %lu, hops: %d)\n",
                  msg->msgType == MSG_DATA ? "DATA" :
                  msg->msgType == MSG_HEARTBEAT ? "HEARTBEAT" :
                  msg->msgType == MSG_ACK ? "ACK" : "OTHER",
                  msg->seqNum, msg->hopCount);
  }
}

void sendHeartbeat() {
  MeshMessage hb;
  hb.msgType = MSG_HEARTBEAT;
  memcpy(hb.srcMAC, myMAC, 6);
  memset(hb.dstMAC, 0xFF, 6);
  hb.hopCount = 0;
  hb.maxHops = 1;
  hb.seqNum = ++messageSeq;
  snprintf((char*)hb.payload, sizeof(hb.payload), "%s", myRole);
  hb.payloadLen = strlen(myRole);

  sendMessage(&hb, broadcastMAC);
}

void sendSensorData() {
  if (!IS_END_DEVICE) return;

  MeshMessage data;
  data.msgType = MSG_DATA;
  memcpy(data.srcMAC, myMAC, 6);
  memset(data.dstMAC, 0xFF, 6);
  data.hopCount = 0;
  data.maxHops = MAX_HOPS;
  data.seqNum = ++messageSeq;

  float temp = 20.0 + (random(0, 100) / 10.0);
  float humidity = 40.0 + (random(0, 200) / 10.0);
  snprintf((char*)data.payload, sizeof(data.payload),
           "TEMP:%.1f,HUM:%.1f", temp, humidity);
  data.payloadLen = strlen((char*)data.payload);

  Serial.println("\n====== SENDING SENSOR DATA ======");
  Serial.printf("Temperature: %.1f C, Humidity: %.1f%%\n", temp, humidity);

  int routeIdx = findBestRoute(data.dstMAC);
  if (routeIdx >= 0) {
    sendMessage(&data, neighbors[routeIdx].mac);
    awaitingAck = true;
    ackSeqNum = data.seqNum;
    ackSentTime = millis();
  } else {
    Serial.println("No route - broadcasting");
    sendMessage(&data, broadcastMAC);
  }
}

// ============ MESSAGE RECEIVING ============
void onDataSent(const uint8_t* mac, esp_now_send_status_t status) {
  Serial.println(status == ESP_NOW_SEND_SUCCESS ?
                 "[DELIVERY] Confirmed" : "[DELIVERY] Failed");
}

void onDataReceived(const esp_now_recv_info_t* info, const uint8_t* data, int len) {
  MeshMessage msg;
  memcpy(&msg, data, sizeof(MeshMessage));
  messagesReceived++;
  flashMessageLED();

  updateNeighbor(info->src_addr,
                 msg.msgType == MSG_HEARTBEAT ? (char*)msg.payload : "UNKNOWN",
                 info->rx_ctrl->rssi);

  Serial.printf("\n[RX] %s from %02X:%02X:%02X:%02X:%02X:%02X\n",
                msg.msgType == MSG_DATA ? "DATA" :
                msg.msgType == MSG_HEARTBEAT ? "HEARTBEAT" :
                msg.msgType == MSG_ACK ? "ACK" : "OTHER",
                info->src_addr[0], info->src_addr[1], info->src_addr[2],
                info->src_addr[3], info->src_addr[4], info->src_addr[5]);

  if (msg.msgType == MSG_DATA) {
    if (IS_COORDINATOR) {
      Serial.println("\n****** DATA AT COORDINATOR ******");
      Serial.printf("Hops: %d, Data: %s\n", msg.hopCount, (char*)msg.payload);
      Serial.println("*********************************\n");

      MeshMessage ack;
      ack.msgType = MSG_ACK;
      memcpy(ack.srcMAC, myMAC, 6);
      memcpy(ack.dstMAC, msg.srcMAC, 6);
      ack.seqNum = msg.seqNum;
      ack.hopCount = 0;
      ack.maxHops = MAX_HOPS;
      sendMessage(&ack, info->src_addr);
      blinkLED(STATUS_LED, 3, 100);

    } else if (IS_ROUTER_A || IS_ROUTER_B) {
      if (msg.hopCount < msg.maxHops) {
        msg.hopCount++;
        messagesRouted++;
        Serial.printf("[RELAY] Forwarding (hop %d)\n", msg.hopCount);

        int nextHopIdx = findBestRoute(msg.dstMAC);
        if (nextHopIdx >= 0) {
          sendMessage(&msg, neighbors[nextHopIdx].mac);
        } else {
          sendMessage(&msg, broadcastMAC);
        }
        blinkLED(MSG_LED, 2, 50);
      }
    }
  } else if (msg.msgType == MSG_ACK) {
    if (msg.seqNum == ackSeqNum && awaitingAck) {
      awaitingAck = false;
      Serial.printf("[ACK] Confirmed for seq %lu\n", msg.seqNum);
      blinkLED(STATUS_LED, 2, 100);
    } else if (IS_ROUTER_A || IS_ROUTER_B) {
      if (msg.hopCount < msg.maxHops) {
        msg.hopCount++;
        int routeIdx = findNeighbor(msg.dstMAC);
        if (routeIdx >= 0) sendMessage(&msg, neighbors[routeIdx].mac);
        else sendMessage(&msg, broadcastMAC);
      }
    }
  }
}

// ============ SETUP ============
void setup() {
  Serial.begin(115200);
  delay(1000);

  pinMode(STATUS_LED, OUTPUT);
  pinMode(MSG_LED, OUTPUT);
  blinkLED(STATUS_LED, 3, 200);

  if (IS_COORDINATOR) strcpy(myRole, "COORDINATOR");
  else if (IS_ROUTER_A || IS_ROUTER_B) strcpy(myRole, "ROUTER");
  else strcpy(myRole, "END_DEVICE");

  Serial.println("\n========================================");
  Serial.println("  Zigbee-Style Mesh Network Simulator");
  Serial.printf("  Node Role: %s\n", myRole);
  Serial.println("========================================\n");

  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  WiFi.macAddress(myMAC);
  Serial.printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\n\n",
                myMAC[0], myMAC[1], myMAC[2], myMAC[3], myMAC[4], myMAC[5]);

  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed!");
    return;
  }

  esp_now_register_send_cb(onDataSent);
  esp_now_register_recv_cb(onDataReceived);

  esp_now_peer_info_t peerInfo = {};
  memcpy(peerInfo.peer_addr, broadcastMAC, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  esp_now_add_peer(&peerInfo);

  if (IS_COORDINATOR) digitalWrite(STATUS_LED, HIGH);

  delay(random(100, 1000));
  sendHeartbeat();
  lastHeartbeat = millis();

  Serial.println("Setup complete!\n");
}

// ============ MAIN LOOP ============
void loop() {
  checkNeighborTimeouts();

  if (millis() - lastHeartbeat > HEARTBEAT_INTERVAL) {
    sendHeartbeat();
    lastHeartbeat = millis();

    static int heartbeatCount = 0;
    if (++heartbeatCount % 3 == 0) {
      printNeighborTable();
      Serial.printf("[STATS] Received: %lu, Routed: %lu\n\n",
                    messagesReceived, messagesRouted);
    }
  }

  if (IS_END_DEVICE && (millis() - lastDataSend > 10000)) {
    sendSensorData();
    lastDataSend = millis();
  }

  if (awaitingAck && (millis() - ackSentTime > ACK_TIMEOUT)) {
    if (ackRetries < MSG_RETRY_COUNT) {
      ackRetries++;
      Serial.printf("[RETRY] %d/%d\n", ackRetries, MSG_RETRY_COUNT);
      ackSentTime = millis();
    } else {
      Serial.println("[RETRY] Failed - triggering route rediscovery");
      awaitingAck = false;
    }
  }

  if ((IS_ROUTER_A || IS_ROUTER_B) && (millis() % 2000 < 100)) {
    digitalWrite(STATUS_LED, HIGH);
  } else if (IS_ROUTER_A || IS_ROUTER_B) {
    digitalWrite(STATUS_LED, LOW);
  }

  delay(10);
}

26.6 Step-by-Step Instructions

26.6.1 Step 1: Set Up Four Simulator Instances

  1. Open four browser tabs with Wokwi ESP32 simulators
  2. Copy the complete code into each tab
  3. Modify the configuration for each node:
Tab Configuration
Tab 1 #define IS_COORDINATOR 1 (others 0)
Tab 2 #define IS_ROUTER_A 1 (others 0)
Tab 3 #define IS_ROUTER_B 1 (others 0)
Tab 4 #define IS_END_DEVICE 1 (default)

26.6.2 Step 2: Start the Network

  1. Start Coordinator first (Tab 1)
  2. Start both Routers (Tabs 2 and 3)
  3. Start End Device last (Tab 4)
  4. Watch Serial Monitor for network formation

26.6.3 Step 3: Observe Mesh Behavior

Watch for these behaviors:

  1. Network Formation: Heartbeat messages establish neighbor table
  2. Multi-Hop Routing: End Device → Router A → Coordinator
  3. ACK Flow: Coordinator → Router A → End Device
  4. Hop Counting: Each relay increments hop count

26.6.4 Step 4: Test Self-Healing

  1. With network running, stop Router A (click pause)
  2. Watch End Device detect timeout (15 seconds)
  3. Observe automatic reroute through Router B
  4. Data continues flowing via backup path

26.7 Key Concepts Demonstrated

26.7.1 Device Roles (Zigbee Device Types)

Lab Role Zigbee Equivalent Behavior
Coordinator ZC Forms network, receives data
Router A/B ZR Relays messages, always on
End Device ZED Sends data, can sleep

26.7.2 Neighbor Table (Like Zigbee Neighbor Table)

Each device maintains a table of nearby devices: - MAC address - Device role - Signal strength (RSSI) - Last seen timestamp - Active status

26.7.3 Self-Healing (AODV Concept)

When Router A fails: 1. End Device detects no ACK 2. Marks route as invalid 3. Discovers new route via Router B 4. Traffic resumes automatically

26.8 Exercises

26.8.1 Exercise 1: Measure Hop Latency

Add timestamps to measure per-hop delay:

// In sendMessage():
Serial.printf("[TX] Time: %lu ms\n", millis());

// In onDataReceived():
Serial.printf("[RX] Time: %lu ms\n", millis());

26.8.2 Exercise 2: Add Second End Device

Create a 5th node as another End Device to see how the mesh handles multiple sources.

26.8.3 Exercise 3: Implement Route Caching

Modify the code to cache the last successful route and try it first before broadcasting.

Sammy the Sensor (the End Device) reports: “I send temperature and humidity readings every 10 seconds to the Coordinator. But I can’t reach it directly – my message has to hop through Router A or Router B first!”

Max the Microcontroller (the Coordinator) explains: “I’m the boss of the network! I receive all the sensor data and display it. Each message comes with a hop count so I know how many devices it passed through to reach me.”

Lila the LED demonstrates: “Watch the LEDs during the experiment! Blue = Coordinator received data, green = Router A forwarded a message, yellow = Router B is the backup path. When I blink rapidly, it means a message is being relayed!”

Bella the Battery adds the best part: “Try unplugging Router A during the simulation. The End Device will notice its messages aren’t being acknowledged, and within seconds it discovers a new path through Router B. That’s mesh self-healing in action!”

Key ideas for kids:

  • ESP-NOW = A simple wireless protocol we use to simulate Zigbee mesh behavior
  • Heartbeat = A periodic ‘I’m alive’ message that devices send to neighbors
  • Route discovery = Finding a new path when the old one breaks
  • ACK timeout = Knowing a message failed because no confirmation came back

26.9 Knowledge Check

Q1: In the Wokwi mesh simulation, what triggers route rediscovery after a Router failure?

  1. The Coordinator periodically scans for offline devices
  2. The End Device fails to receive an ACK after multiple retry attempts
  3. Router B sends a notification that Router A is offline
  4. A timer automatically rotates routes every 30 seconds

B) The End Device fails to receive an ACK after multiple retry attempts – When the End Device sends a message through Router A and receives no acknowledgment (ACK) after the configured retry count (typically 3 attempts), it marks the route as broken and initiates a new route discovery. Option A is wrong because Zigbee uses reactive routing, not coordinator-initiated polling. Option C is wrong because Router B has no awareness of Router A’s state – there is no inter-router health notification mechanism. Option D is wrong because route rotation is not a Zigbee feature; routes remain stable until a failure is detected.

Scenario: You’ve built the 4-node ESP-NOW mesh simulator (1 Coordinator, 2 Routers, 1 End Device) and notice that when Router A fails, the End Device takes 2-3 seconds to discover the alternate route through Router B. A production deployment requires <500ms recovery time.

Problem Analysis:

Current topology has only 1 alternate path. When Router A fails: - MAC ACK timeout: 500ms × 3 retries = 1500ms (as configured in the lab code) - Neighbor timeout detection: up to 15000ms (ROUTE_TIMEOUT) - Route table update: 50ms - Total: 1.5-15 seconds (far exceeds 500ms requirement)

Solution Steps:

  1. Add Router C at position (1, 1) to create a denser mesh:

    • Original: E → R1 → C or E → R2 → C (2 paths)
    • Enhanced: E → R1 → C, E → R2 → C, E → R3 → C (3 paths)
  2. Modify discovery parameters in the code:

    #define ACK_TIMEOUT 80                 // Reduce from 500ms
    #define MSG_RETRY_COUNT 2              // Reduce from 3
    #define ROUTE_TIMEOUT 3000             // Reduce from 15000ms
  3. Pre-populate neighbor tables during initialization to avoid cold-start delays:

    void setup() {
      // ... existing code ...
      discoverNeighbors();
      buildRoutingTables();  // Pre-compute 2-hop paths
    }

Expected Improvement:

  • ACK timeout: 80ms x 2 retries = 160ms (saved 1340ms vs original 1500ms)
  • Route timeout: 3000ms reduced to allow faster neighbor table cleanup
  • Pre-populated neighbors eliminate cold-start discovery delay
  • New total: 210-400ms (meets <500ms requirement)

Real-World Application: Dense mesh deployments (1 router per 10-15m) provide multiple alternate paths, reducing worst-case recovery time from seconds to hundreds of milliseconds. Critical control applications (industrial safety, smart lighting) require 3-5× minimum router density for reliable sub-second recovery.

Key Insight: Mesh density trades cost (more routers) for reliability (faster recovery). Calculate worst-case failure scenarios during planning: if losing any single router must not exceed X ms recovery, deploy enough routers so every device has at least 2-3 viable paths.

Challenge: The current simulation takes 2-3 seconds to recover when Router A fails. Reduce this to <500ms.

Modifications to try:

  1. Reduce ACK timeout from 500ms to 100ms (faster failure detection)
  2. Pre-populate neighbor tables during setup (skip discovery phase)
  3. Add third router (more alternate paths available)
  4. Implement cached backup route (store 2nd-best path proactively)

Expected improvements:

  • 100ms ACK timeout × 3 retries = 300ms (saves 1.2 sec)
  • Cached routes eliminate RREQ broadcast phase (saves 500ms)
  • Third router provides immediate alternate without discovery

Learning objective: Understand trade-offs between network overhead (frequent updates) and recovery speed.

26.10 Concept Relationships

Concept Relationship to Lab Hands-On Demonstration
ESP-NOW Zigbee simulation protocol Peer-to-peer messaging without WiFi infrastructure
Neighbor Tables Route discovery foundation Each device tracks nearby nodes with RSSI
Hop Counting Multi-hop routing metric Each relay increments hop count
ACK Timeout Failure detection mechanism 3 retries × 500ms = 1.5 sec failure detection
Self-Healing Mesh resilience Automatic route rediscovery after link failure

26.11 See Also

Common Pitfalls

Zigbee lab exercises fail unexpectedly when conducted in offices with many competing 2.4 GHz networks. Check channel occupancy with a spectrum analyzer or Wi-Fi scanner and select a clear Zigbee channel before starting labs.

Zigbee stack bugs fixed in firmware updates can cause erratic behavior in lab exercises. Update all lab hardware to the latest stable firmware before conducting exercises.

Real hardware often produces unexpected behaviors (intermittent routing failures, association timeouts) that are highly instructive. Document unexpected behaviors with packet captures rather than dismissing them as ‘hardware issues’.

:

26.12 What’s Next

Chapter Focus
Zigbee Routing AODV protocol details behind the self-healing behaviour observed in this lab
Zigbee Network Topologies Star, tree, and mesh topology design patterns for production deployments
Zigbee Common Mistakes Real-world deployment pitfalls including router placement and channel interference
Zigbee Hands-On Labs Additional practical exercises building on the mesh concepts from this lab
Zigbee Security AES-128 encryption and key management for securing mesh traffic