// ============================================================
// 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);
}