This FANET lab implements mesh networking with 2-second beacon intervals across a 2 km air-to-air range, multi-hop relay with 5-hop TTL limits, and leader election within 5-second timeouts favoring drones with battery above 30%. Five formation patterns (line, wedge, circle, grid, search) offer different coverage-connectivity-energy tradeoffs, with emergency protocols triggering return-to-home below 20% battery.
Putting Numbers to It
Lab execution time can be estimated before starting runs:
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.
47.1 Learning Objectives
By the end of this lab, you will be able to:
Implement FANET mesh networking with beacon-based neighbor discovery broadcasting position data every 2 seconds across a 2 km communication range
Design multi-hop message relay paths that forward telemetry through intermediate drone nodes within a 5-hop TTL limit to reach the ground station
Analyze swarm coordination algorithms including leader election based on battery level and connectivity metrics
Calculate GPS-based inter-drone distances using the Haversine formula and evaluate link quality from signal strength, battery, and age factors
Evaluate formation patterns (line, wedge, circle, grid, search) by comparing coverage area, mesh connectivity, and energy consumption trade-offs
Implement fault-tolerant emergency protocols that detect low-battery conditions below 20% and trigger return-to-home with mesh-relayed alerts
Minimum Viable Understanding
Before starting this lab, ensure you grasp these essentials:
FANET beacon interval of 2 seconds determines how frequently drones broadcast their GPS position and state to neighbors within the 2 km air-to-air communication range
Multi-hop routing with TTL = 5 allows telemetry messages to traverse up to 5 relay nodes to reach the ground station; messages exceeding this limit are dropped to prevent network flooding
Leader election timeout of 5 seconds means the swarm will detect a missing coordinator within 5 seconds and automatically trigger a new election favoring the drone with the highest battery level above 30%
Sensor Squad: Drone Swarm Communication
Imagine you and your friends are playing a giant game of tag in a park, but you cannot shout across the whole park!
Max (motion) says: “I am like a drone flying through the sky! I keep telling my nearby friends where I am every 2 seconds – that is my beacon. If a friend is too far away (more than 2 kilometers), they cannot hear me.”
Lila (light) adds: “When Max needs to send a message to the teacher at the base station, but the teacher is too far away, the message gets passed from friend to friend – like a relay race! Each pass is called a hop, and we allow up to 5 hops before the message expires.”
Sammy (sound) explains: “What happens if the team captain goes home early? We pick a new captain! The friend with the most energy (battery) becomes the new leader. We check every 5 seconds to make sure our captain is still around.”
Bella (bio/button) concludes: “And if any friend is running low on energy – below 20% – they send an emergency signal and head home safely. The rest of the team closes the gap and keeps searching!”
For Beginners: What Is a Flying Ad-Hoc Network?
A Flying Ad-Hoc Network (FANET) is a group of drones that talk to each other without any cell towers or Wi-Fi routers. Each drone acts as both a communicator and a relay station.
Think of it like walkie-talkies: each drone regularly announces “I am here!” (a beacon) so nearby drones know who is around. If a drone needs to send information to the ground control but is too far away, it asks a closer drone to pass the message along – this is called multi-hop routing.
Key ideas in this lab:
Neighbor discovery: Each drone broadcasts its location every 2 seconds. Other drones within range (2 km) add it to their contact list.
Message relay: If the ground station is out of direct range, messages hop through other drones, up to 5 times maximum.
Leader election: One drone coordinates the group. If that drone fails, the group automatically picks a new leader – the one with the most battery remaining.
Formations: Drones can fly in patterns (line, V-shape, circle, grid, spread out) depending on the mission goal.
This lab simulates all of these concepts on a single ESP32 microcontroller, using software to model 4 virtual drones.
47.2 Prerequisites
Before diving into this lab, you should be familiar with:
UAV Introduction: Basic UAV network concepts and FANET fundamentals
ESP32 FANET Simulation: Using Wokwi-simulated ESP32 microcontrollers to model FANET nodes, simulating drone-to-drone communication, swarm behavior, and GPS coordinate handling without physical hardware
Drone-to-Drone Communication: Direct radio communication between UAV nodes without a central coordinator — simulated using ESP32 ESP-NOW (low-latency peer-to-peer Wi-Fi) or UART bridging in Wokwi
Swarm Behavior Patterns: Flocking (cohesion, separation, alignment), formation (leader-follower, V-formation), and coverage (Voronoi partition) — implemented as state machines in ESP32 firmware
GPS Coordinate Handling: Representing UAV positions as latitude/longitude/altitude tuples and calculating distances (Haversine formula) for relative positioning between swarm nodes
Simulated Radio Range: Modeling communication range constraints in Wokwi by checking node distance against a configurable range threshold before forwarding packets
FANET Topology Visualization: Displaying the current network graph (nodes, links, missing connections) as ASCII art or serial plotter output for understanding dynamic topology changes
Failure Injection Testing: Simulating UAV failure by disabling a Wokwi node and observing how the remaining swarm adapts — validates fault tolerance without physical crash risk
47.3 FANET Architecture Overview
The following diagram shows how the 4-drone FANET operates in this lab, including beacon exchange, multi-hop telemetry relay, leader election, and ground station communication.
47.4 Interactive Lab: UAV/Drone Communication Network
Hands-On Learning: FANET Simulation with ESP32
This interactive lab simulates a Flying Ad-hoc Network (FANET) using ESP32 microcontrollers. You’ll explore drone-to-drone communication, ground station coordination, swarm behavior patterns, and GPS coordinate handling - all concepts central to UAV network design.
What You’ll Learn:
FANET Mesh Networking - How drones discover neighbors and form ad-hoc networks
Multi-Hop Message Relay - Forwarding data through intermediate drone nodes
Swarm Coordination - Distributed algorithms for collective behavior
GPS Data Handling - Parsing, validating, and sharing location data
Ground Station Integration - Central command and telemetry aggregation
Leader Election - Selecting coordinator nodes in distributed systems
Formation Control - Maintaining relative positions in a swarm
Lab Scenario: You are deploying a 4-drone search and rescue swarm. Each drone must: - Broadcast its position to neighbors - Relay emergency beacon signals toward the ground station - Maintain formation while searching - Elect a leader if the current leader goes offline
47.4.1 Wokwi Simulation Environment
Setup Instructions
Copy the code below into the Wokwi editor
Click “Start Simulation” to run the FANET simulation
Open the Serial Monitor (115200 baud) to observe drone communications
Watch the console for swarm coordination messages, GPS updates, and relay operations
The simulation models 4 virtual drones communicating through a simulated mesh network, demonstrating real FANET concepts on a single ESP32.
47.4.2 Complete Lab Code: FANET Drone Communication System
/* * ============================================================================ * UAV/FANET Communication Lab - ESP32 Drone Network Simulation * ============================================================================ * * This lab simulates a Flying Ad-hoc Network (FANET) with 4 virtual drones * demonstrating key UAV networking concepts: * * 1. Mesh Network Formation - Neighbor discovery and link establishment * 2. Multi-Hop Routing - Message relay through intermediate nodes * 3. Swarm Coordination - Distributed behavior patterns * 4. GPS Handling - Position tracking and sharing * 5. Ground Station Communication - Telemetry and command relay * 6. Leader Election - Coordinator selection algorithms * 7. Formation Control - Relative position maintenance * * Educational IoT Course - UAV Networks Module * ============================================================================ */#include <Arduino.h>#include <math.h>// ============================================================================// CONFIGURATION CONSTANTS// ============================================================================// Network Configuration#define NUM_DRONES 4#define GROUND_STATION_ID 0xFF#define BROADCAST_ID 0xFE#define MAX_NEIGHBORS 6#define COMM_RANGE_METERS 2000.0// 2 km air-to-air range#define MESSAGE_QUEUE_SIZE 10#define MAX_HOPS 5// Timing Configuration (milliseconds)#define BEACON_INTERVAL 2000// Position broadcast interval#define HEARTBEAT_INTERVAL 1000// Alive signal interval#define SWARM_UPDATE_INTERVAL 500// Formation update rate#define TELEMETRY_INTERVAL 3000// Ground station report#define LEADER_TIMEOUT 5000// Leader heartbeat timeout#define NEIGHBOR_TIMEOUT 6000// Link expiry time// GPS Simulation Bounds (decimal degrees)#define BASE_LATITUDE 37.7749// San Francisco area#define BASE_LONGITUDE -122.4194#define GPS_VARIANCE 0.005// ~500m movement range// Swarm Formation Parameters#define FORMATION_SPACING 100.0// meters between drones#define SEARCH_ALTITUDE 120.0// meters (regulatory max)// ============================================================================// DATA STRUCTURES// ============================================================================// Message Types for FANET Protocolenum MessageType { MSG_BEACON,// Position broadcast MSG_HEARTBEAT,// Alive signal MSG_TELEMETRY,// Sensor data to ground MSG_COMMAND,// Ground station command MSG_RELAY,// Multi-hop forwarded message MSG_EMERGENCY,// High-priority alert MSG_LEADER_ANNOUNCE,// Leader election announcement MSG_LEADER_VOTE,// Leader election vote MSG_FORMATION_CMD,// Formation control command MSG_SWARM_STATE // Collective swarm status};// Drone Operational Statesenum DroneState { STATE_INITIALIZING, STATE_SEARCHING, STATE_HOVERING, STATE_RETURNING, STATE_RELAYING, STATE_EMERGENCY, STATE_LEADER};// Swarm Formation Typesenum FormationType { FORM_LINE, FORM_WEDGE, FORM_CIRCLE, FORM_GRID, FORM_SEARCH_PATTERN};// GPS Coordinates Structurestruct GPSCoordinate {double latitude;double longitude;float altitude;// metersfloat accuracy;// meters (GPS precision)unsignedlong timestamp;bool valid;};// Neighbor Informationstruct Neighbor {uint8_t droneId; GPSCoordinate position;float signalStrength;// dBmfloat distance;// metersunsignedlong lastSeen;bool isAlive; DroneState state;float batteryLevel;};// Network Message Structurestruct FANETMessage {uint8_t sourceId;uint8_t destId; MessageType type;uint8_t hopCount;uint8_t ttl;// Time to live (max hops)unsignedlong timestamp;uint8_t dataLength;uint8_t data[64];};// Drone Telemetry Datastruct DroneTelemetry {uint8_t droneId; GPSCoordinate position;float batteryLevel;// 0-100%float batteryVoltage;// Volts DroneState state;float heading;// degreesfloat speed;// m/sfloat verticalSpeed;// m/suint8_t neighborCount;uint8_t signalQuality;// 0-100%bool hasTarget;// Search target detectedfloat targetBearing;// degrees to target};// Swarm Statestruct SwarmState {uint8_t leaderId; FormationType formation; GPSCoordinate centroid;// Swarm centeruint8_t activeDrones;float coverageArea;// km squaredunsignedlong missionTime;bool targetFound; GPSCoordinate targetLocation;};// ============================================================================// VIRTUAL DRONE CLASS// ============================================================================class VirtualDrone {public:uint8_t id; GPSCoordinate position; DroneTelemetry telemetry; Neighbor neighbors[MAX_NEIGHBORS];uint8_t neighborCount; DroneState state;bool isLeader;// Timingunsignedlong lastBeacon;unsignedlong lastHeartbeat;unsignedlong lastTelemetry;unsignedlong lastSwarmUpdate;unsignedlong lastLeaderHeartbeat;// Formation positionint formationIndex; GPSCoordinate targetPosition;// Mission statefloat searchProgress;bool targetDetected; VirtualDrone(){ neighborCount =0; state = STATE_INITIALIZING; isLeader =false; lastBeacon =0; lastHeartbeat =0; lastTelemetry =0; lastSwarmUpdate =0; lastLeaderHeartbeat =0; searchProgress =0; targetDetected =false;}void initialize(uint8_t droneId,int formIdx){ id = droneId; formationIndex = formIdx;// Initialize GPS position with offset based on formation index position.latitude = BASE_LATITUDE +(formIdx *0.001); position.longitude = BASE_LONGITUDE +(formIdx *0.001); position.altitude = SEARCH_ALTITUDE; position.accuracy =2.5;// meters position.timestamp = millis(); position.valid =true;// Initialize telemetry telemetry.droneId = droneId; telemetry.position = position; telemetry.batteryLevel =95.0-(droneId *5);// Varied battery levels telemetry.batteryVoltage =22.2*(telemetry.batteryLevel /100.0); telemetry.state = state; telemetry.heading = random(0,360); telemetry.speed =8.0;// m/s survey speed telemetry.verticalSpeed =0; telemetry.neighborCount =0; telemetry.signalQuality =85; telemetry.hasTarget =false;// Set target position (search waypoint) targetPosition = position; targetPosition.latitude +=0.002;// ~200m north state = STATE_SEARCHING; Serial.printf("[DRONE-%d] Initialized at (%.6f, %.6f) alt=%.1fm\n", id, position.latitude, position.longitude, position.altitude);}// Update GPS position (simulated movement)void updatePosition(){// Simulate movement toward targetdouble dLat = targetPosition.latitude - position.latitude;double dLon = targetPosition.longitude - position.longitude;double dist = sqrt(dLat*dLat + dLon*dLon);if(dist >0.0001){// ~11 meters// Move 1% toward target each update position.latitude += dLat *0.01; position.longitude += dLon *0.01;// Add small random variance (GPS jitter) position.latitude +=(random(-100,100)/1000000.0); position.longitude +=(random(-100,100)/1000000.0);}else{// Reached waypoint - set new random target targetPosition.latitude = BASE_LATITUDE +(random(-500,500)/100000.0); targetPosition.longitude = BASE_LONGITUDE +(random(-500,500)/100000.0); searchProgress +=5.0;}// Update telemetry position.timestamp = millis(); telemetry.position = position; telemetry.heading = atan2(dLon, dLat)*180.0/ PI;if(telemetry.heading <0) telemetry.heading +=360;// Simulate battery drain telemetry.batteryLevel -=0.01; telemetry.batteryVoltage =22.2*(telemetry.batteryLevel /100.0);if(telemetry.batteryLevel <20.0){ state = STATE_RETURNING;}}// Calculate distance to another dronefloat distanceTo(GPSCoordinate& other){// Haversine formula for GPS distancedouble R =6371000;// Earth radius in metersdouble dLat =(other.latitude - position.latitude)* PI /180.0;double dLon =(other.longitude - position.longitude)* PI /180.0;double a = sin(dLat/2)* sin(dLat/2)+ cos(position.latitude * PI /180.0)* cos(other.latitude * PI /180.0)* sin(dLon/2)* sin(dLon/2);double c =2* atan2(sqrt(a), sqrt(1-a));float horizontalDist = R * c;// Include altitude differencefloat altDiff = other.altitude - position.altitude;return sqrt(horizontalDist * horizontalDist + altDiff * altDiff);}// Check if another position is within communication rangebool isInRange(GPSCoordinate& other){return distanceTo(other)<= COMM_RANGE_METERS;}// Update neighbor informationvoid updateNeighbor(uint8_t neighborId, GPSCoordinate& pos, DroneState neighborState,float battery){// Check if already in neighbor listfor(int i =0; i < neighborCount; i++){if(neighbors[i].droneId == neighborId){ neighbors[i].position = pos; neighbors[i].distance = distanceTo(pos); neighbors[i].lastSeen = millis(); neighbors[i].isAlive =true; neighbors[i].state = neighborState; neighbors[i].batteryLevel = battery;// Simulate signal strength based on distance neighbors[i].signalStrength =-50.0-(neighbors[i].distance /50.0);return;}}// Add new neighborif(neighborCount < MAX_NEIGHBORS){ neighbors[neighborCount].droneId = neighborId; neighbors[neighborCount].position = pos; neighbors[neighborCount].distance = distanceTo(pos); neighbors[neighborCount].lastSeen = millis(); neighbors[neighborCount].isAlive =true; neighbors[neighborCount].state = neighborState; neighbors[neighborCount].batteryLevel = battery; neighbors[neighborCount].signalStrength =-50.0-(neighbors[neighborCount].distance /50.0); neighborCount++;}}// Remove stale neighborsvoid pruneNeighbors(){unsignedlong now = millis();for(int i = neighborCount -1; i >=0; i--){if(now - neighbors[i].lastSeen > NEIGHBOR_TIMEOUT){ Serial.printf("[DRONE-%d] Lost contact with Drone-%d\n", id, neighbors[i].droneId);// Remove by shifting arrayfor(int j = i; j < neighborCount -1; j++){ neighbors[j]= neighbors[j +1];} neighborCount--;}} telemetry.neighborCount = neighborCount;}// Simulate target detection (for search missions)void checkForTarget(){// Random chance of detecting target based on search progressif(!targetDetected && random(0,1000)<2){// 0.2% chance per check targetDetected =true; telemetry.hasTarget =true; telemetry.targetBearing = random(0,360); state = STATE_HOVERING; Serial.printf("\n[DRONE-%d] *** TARGET DETECTED at bearing %.0f deg ***\n\n", id, telemetry.targetBearing);}}};// ============================================================================// FANET MESSAGE HANDLING// ============================================================================class FANETNetwork {public: FANETMessage messageQueue[MESSAGE_QUEUE_SIZE];uint8_t queueHead;uint8_t queueTail;uint8_t queueCount;// Swarm coordination SwarmState swarmState;uint8_t currentLeaderId;unsignedlong lastLeaderAnnounce;bool leaderElectionInProgress; FANETNetwork(){ queueHead =0; queueTail =0; queueCount =0; currentLeaderId =0xFF; lastLeaderAnnounce =0; leaderElectionInProgress =false; swarmState.leaderId =0xFF; swarmState.formation = FORM_SEARCH_PATTERN; swarmState.activeDrones =0; swarmState.coverageArea =0; swarmState.missionTime =0; swarmState.targetFound =false;}// Queue a message for transmissionbool queueMessage(FANETMessage& msg){if(queueCount >= MESSAGE_QUEUE_SIZE){returnfalse;} messageQueue[queueTail]= msg; queueTail =(queueTail +1)% MESSAGE_QUEUE_SIZE; queueCount++;returntrue;}// Dequeue next messagebool dequeueMessage(FANETMessage& msg){if(queueCount ==0){returnfalse;} msg = messageQueue[queueHead]; queueHead =(queueHead +1)% MESSAGE_QUEUE_SIZE; queueCount--;returntrue;}// Create beacon message FANETMessage createBeacon(VirtualDrone& drone){ FANETMessage msg; msg.sourceId = drone.id; msg.destId = BROADCAST_ID; msg.type = MSG_BEACON; msg.hopCount =0; msg.ttl =1;// Single hop for beacons msg.timestamp = millis();// Pack position data memcpy(msg.data,&drone.position,sizeof(GPSCoordinate)); msg.data[sizeof(GPSCoordinate)]=(uint8_t)drone.state; msg.data[sizeof(GPSCoordinate)+1]=(uint8_t)drone.telemetry.batteryLevel; msg.dataLength =sizeof(GPSCoordinate)+2;return msg;}// Create telemetry message for ground station FANETMessage createTelemetry(VirtualDrone& drone){ FANETMessage msg; msg.sourceId = drone.id; msg.destId = GROUND_STATION_ID; msg.type = MSG_TELEMETRY; msg.hopCount =0; msg.ttl = MAX_HOPS; msg.timestamp = millis(); memcpy(msg.data,&drone.telemetry,sizeof(DroneTelemetry)); msg.dataLength =sizeof(DroneTelemetry);return msg;}// Create emergency message FANETMessage createEmergency(VirtualDrone& drone,constchar* reason){ FANETMessage msg; msg.sourceId = drone.id; msg.destId = GROUND_STATION_ID; msg.type = MSG_EMERGENCY; msg.hopCount =0; msg.ttl = MAX_HOPS; msg.timestamp = millis();// Pack emergency data memcpy(msg.data,&drone.position,sizeof(GPSCoordinate)); strncpy((char*)(msg.data +sizeof(GPSCoordinate)), reason,20); msg.dataLength =sizeof(GPSCoordinate)+20;return msg;}// Create leader announcement FANETMessage createLeaderAnnounce(VirtualDrone& drone){ FANETMessage msg; msg.sourceId = drone.id; msg.destId = BROADCAST_ID; msg.type = MSG_LEADER_ANNOUNCE; msg.hopCount =0; msg.ttl = MAX_HOPS; msg.timestamp = millis(); msg.data[0]= drone.id; msg.data[1]=(uint8_t)drone.telemetry.batteryLevel; msg.dataLength =2;return msg;}// Process received messagevoid processMessage(FANETMessage& msg, VirtualDrone drones[],int numDrones){switch(msg.type){case MSG_BEACON: processBeacon(msg, drones, numDrones);break;case MSG_TELEMETRY: processTelemetry(msg);break;case MSG_EMERGENCY: processEmergency(msg);break;case MSG_LEADER_ANNOUNCE: processLeaderAnnounce(msg, drones, numDrones);break;case MSG_RELAY: processRelay(msg, drones, numDrones);break;default:break;}}// Process beacon - update neighbor tablesvoid processBeacon(FANETMessage& msg, VirtualDrone drones[],int numDrones){ GPSCoordinate beaconPos; memcpy(&beaconPos, msg.data,sizeof(GPSCoordinate)); DroneState beaconState =(DroneState)msg.data[sizeof(GPSCoordinate)];float beaconBattery = msg.data[sizeof(GPSCoordinate)+1];// Update all drones' neighbor tablesfor(int i =0; i < numDrones; i++){if(drones[i].id != msg.sourceId){if(drones[i].isInRange(beaconPos)){ drones[i].updateNeighbor(msg.sourceId, beaconPos, beaconState, beaconBattery);}}}}// Process telemetry at ground stationvoid processTelemetry(FANETMessage& msg){ DroneTelemetry telem; memcpy(&telem, msg.data,sizeof(DroneTelemetry)); Serial.printf("[GCS] Telemetry from Drone-%d: ", telem.droneId); Serial.printf("Pos(%.6f,%.6f,%.1fm) ", telem.position.latitude, telem.position.longitude, telem.position.altitude); Serial.printf("Batt=%.1f%% ", telem.batteryLevel); Serial.printf("Neighbors=%d ", telem.neighborCount); Serial.printf("State=%d ",(int)telem.state);if(telem.hasTarget){ Serial.printf("TARGET@%.0fdeg", telem.targetBearing);} Serial.println();// Update swarm state swarmState.activeDrones++;if(telem.hasTarget &&!swarmState.targetFound){ swarmState.targetFound =true; swarmState.targetLocation = telem.position; Serial.println("\n[GCS] *** SWARM TARGET ACQUISITION CONFIRMED ***\n");}}// Process emergency messagevoid processEmergency(FANETMessage& msg){ GPSCoordinate emergPos; memcpy(&emergPos, msg.data,sizeof(GPSCoordinate));char reason[21]; strncpy(reason,(char*)(msg.data +sizeof(GPSCoordinate)),20); reason[20]='\0'; Serial.printf("\n[GCS] !!! EMERGENCY from Drone-%d !!!\n", msg.sourceId); Serial.printf("[GCS] Location: (%.6f, %.6f, %.1fm)\n", emergPos.latitude, emergPos.longitude, emergPos.altitude); Serial.printf("[GCS] Reason: %s\n\n", reason);}// Process leader announcementvoid processLeaderAnnounce(FANETMessage& msg, VirtualDrone drones[],int numDrones){uint8_t announcedLeader = msg.data[0];uint8_t leaderBattery = msg.data[1];// Simple leader election: highest ID with sufficient battery winsif(announcedLeader > currentLeaderId ||(currentLeaderId ==0xFF&& leaderBattery >30)){ currentLeaderId = announcedLeader; lastLeaderAnnounce = millis(); swarmState.leaderId = currentLeaderId;// Update drone statesfor(int i =0; i < numDrones; i++){if(drones[i].id == currentLeaderId){ drones[i].isLeader =true; Serial.printf("[SWARM] Drone-%d elected as SWARM LEADER\n", currentLeaderId);}else{ drones[i].isLeader =false;}}}}// Process relay message - multi-hop forwardingvoid processRelay(FANETMessage& msg, VirtualDrone drones[],int numDrones){if(msg.hopCount >= msg.ttl){ Serial.printf("[RELAY] Message from Drone-%d expired (TTL=%d)\n", msg.sourceId, msg.ttl);return;}// Forward to destination or next hop msg.hopCount++;// Find next hop toward destinationif(msg.destId == GROUND_STATION_ID){// Ground station - forward toward lowest ID drone (closest to GCS) Serial.printf("[RELAY] Forwarding msg from Drone-%d to GCS (hop %d/%d)\n", msg.sourceId, msg.hopCount, msg.ttl);}// Re-queue for transmission queueMessage(msg);}// Check for leader timeout and trigger electionvoid checkLeaderStatus(VirtualDrone drones[],int numDrones){unsignedlong now = millis();if(currentLeaderId !=0xFF&& now - lastLeaderAnnounce > LEADER_TIMEOUT){ Serial.printf("[SWARM] Leader Drone-%d timeout - starting election\n", currentLeaderId); currentLeaderId =0xFF; leaderElectionInProgress =true;// Find best candidateuint8_t bestCandidate =0xFF;float bestBattery =0;for(int i =0; i < numDrones; i++){if(drones[i].telemetry.batteryLevel > bestBattery && drones[i].state != STATE_RETURNING){ bestBattery = drones[i].telemetry.batteryLevel; bestCandidate = drones[i].id;}}if(bestCandidate !=0xFF){ FANETMessage announce = createLeaderAnnounce(drones[bestCandidate]); queueMessage(announce);}}}// Calculate swarm metricsvoid updateSwarmState(VirtualDrone drones[],int numDrones){ swarmState.activeDrones =0;double sumLat =0, sumLon =0, sumAlt =0;for(int i =0; i < numDrones; i++){if(drones[i].state != STATE_RETURNING){ swarmState.activeDrones++; sumLat += drones[i].position.latitude; sumLon += drones[i].position.longitude; sumAlt += drones[i].position.altitude;}}if(swarmState.activeDrones >0){ swarmState.centroid.latitude = sumLat / swarmState.activeDrones; swarmState.centroid.longitude = sumLon / swarmState.activeDrones; swarmState.centroid.altitude = sumAlt / swarmState.activeDrones;} swarmState.missionTime = millis()/1000;}};// ============================================================================// FORMATION CONTROL// ============================================================================class FormationController {public: FormationType currentFormation;float spacing; GPSCoordinate leaderPosition; FormationController(){ currentFormation = FORM_SEARCH_PATTERN; spacing = FORMATION_SPACING;}// Calculate formation position for each drone GPSCoordinate getFormationPosition(uint8_t droneIndex, GPSCoordinate& center,float heading){ GPSCoordinate pos = center;float offsetX =0, offsetY =0;// Convert heading to radiansfloat headRad = heading * PI /180.0;switch(currentFormation){case FORM_LINE:// Line formation perpendicular to heading offsetX =(droneIndex -1.5)* spacing;break;case FORM_WEDGE:// V-formation behind leaderif(droneIndex ==0){// Leader at front}else{int side =(droneIndex %2==0)?1:-1;int depth =(droneIndex +1)/2; offsetX = side * depth * spacing *0.7; offsetY =-depth * spacing;}break;case FORM_CIRCLE:// Circle formation{float angle =(2* PI * droneIndex)/ NUM_DRONES; offsetX = spacing * cos(angle); offsetY = spacing * sin(angle);}break;case FORM_GRID:// 2x2 grid offsetX =((droneIndex %2)-0.5)* spacing; offsetY =((droneIndex /2)-0.5)* spacing;break;case FORM_SEARCH_PATTERN:// Spread out for maximum coverage{float angle =(2* PI * droneIndex)/ NUM_DRONES;float radius = spacing *2;// Wider spread offsetX = radius * cos(angle); offsetY = radius * sin(angle);}break;}// Rotate offsets by headingfloat rotX = offsetX * cos(headRad)- offsetY * sin(headRad);float rotY = offsetX * sin(headRad)+ offsetY * cos(headRad);// Convert meter offsets to GPS coordinates// Approximate: 1 degree latitude = 111 km// 1 degree longitude = 111 km * cos(latitude) pos.latitude += rotY /111000.0; pos.longitude += rotX /(111000.0* cos(pos.latitude * PI /180.0));return pos;}// Print formation statusvoid printFormationStatus(VirtualDrone drones[],int numDrones){ Serial.println("\n[FORMATION] Current Swarm Positions:"); Serial.println("+---------+-------------+---------------+--------+----------+"); Serial.println("| Drone | Latitude | Longitude | Alt(m) | State |"); Serial.println("+---------+-------------+---------------+--------+----------+");for(int i =0; i < numDrones; i++){constchar* stateStr;switch(drones[i].state){case STATE_INITIALIZING: stateStr ="INIT";break;case STATE_SEARCHING: stateStr ="SEARCH";break;case STATE_HOVERING: stateStr ="HOVER";break;case STATE_RETURNING: stateStr ="RETURN";break;case STATE_RELAYING: stateStr ="RELAY";break;case STATE_EMERGENCY: stateStr ="EMERG";break;case STATE_LEADER: stateStr ="LEADER";break;default: stateStr ="UNKNOWN";break;} Serial.printf("| Drone-%d | %11.6f | %13.6f | %6.1f | %-8s |\n", drones[i].id, drones[i].position.latitude, drones[i].position.longitude, drones[i].position.altitude, stateStr);} Serial.println("+---------+-------------+---------------+--------+----------+\n");}};// ============================================================================// ROUTING PROTOCOL// ============================================================================class FANETRouter {public:// Find best next hop toward destinationuint8_t findNextHop(VirtualDrone& source,uint8_t destId, VirtualDrone drones[],int numDrones){if(destId == BROADCAST_ID){return BROADCAST_ID;}// For ground station, route toward drone 0 (designated relay)if(destId == GROUND_STATION_ID){// Find neighbor closest to drone 0float minDist =999999;uint8_t bestHop = GROUND_STATION_ID;for(int i =0; i < source.neighborCount; i++){if(source.neighbors[i].droneId ==0){// Direct path to relay dronereturn0;}// Find neighbor closest to drone 0for(int j =0; j < numDrones; j++){if(drones[j].id == source.neighbors[i].droneId){float dist = drones[j].distanceTo(drones[0].position);if(dist < minDist){ minDist = dist; bestHop = source.neighbors[i].droneId;}break;}}}return bestHop;}// Direct destination checkfor(int i =0; i < source.neighborCount; i++){if(source.neighbors[i].droneId == destId){return destId;}}// No route foundreturn0xFF;}// Calculate route quality metricfloat calculateLinkQuality(Neighbor& neighbor){// Combine signal strength and battery levelfloat signalFactor =(neighbor.signalStrength +100)/50.0;// Normalizefloat batteryFactor = neighbor.batteryLevel /100.0;float ageFactor =1.0-((millis()- neighbor.lastSeen)/(float)NEIGHBOR_TIMEOUT);return signalFactor *0.4+ batteryFactor *0.3+ ageFactor *0.3;}};// ============================================================================// GLOBAL INSTANCES// ============================================================================VirtualDrone drones[NUM_DRONES];FANETNetwork network;FormationController formation;FANETRouter router;// Timingunsignedlong lastStatusPrint =0;unsignedlong simulationStart =0;constunsignedlong STATUS_PRINT_INTERVAL =5000;// ============================================================================// MAIN SETUP// ============================================================================void setup(){ Serial.begin(115200); delay(1000); Serial.println("\n"); Serial.println("================================================================"); Serial.println(" UAV/FANET COMMUNICATION LAB - ESP32 DRONE SIMULATION "); Serial.println("================================================================"); Serial.println(" This lab demonstrates Flying Ad-hoc Network (FANET) concepts: "); Serial.println(" - Mesh network formation and neighbor discovery "); Serial.println(" - Multi-hop message relay to ground station "); Serial.println(" - Swarm coordination and leader election "); Serial.println(" - GPS position tracking and sharing "); Serial.println(" - Search and rescue mission simulation "); Serial.println("================================================================\n");// Initialize random seed randomSeed(analogRead(0));// Initialize drones Serial.println("[SYSTEM] Initializing UAV Swarm...\n");for(int i =0; i < NUM_DRONES; i++){ drones[i].initialize(i, i); delay(100);}// Drone 0 is initially the leader (closest to ground station) drones[0].isLeader =true; network.currentLeaderId =0; network.lastLeaderAnnounce = millis(); Serial.println("\n[SYSTEM] Swarm initialized. Starting FANET operations...\n"); Serial.println("========================================================\n"); simulationStart = millis();}// ============================================================================// MAIN LOOP// ============================================================================void loop(){unsignedlong currentTime = millis();// ===========================================// UPDATE EACH DRONE// ===========================================for(int i =0; i < NUM_DRONES; i++){ VirtualDrone& drone = drones[i];// Update position (simulated GPS movement) drone.updatePosition();// Check for target (search mission) drone.checkForTarget();// Prune stale neighbors drone.pruneNeighbors();// Send beaconif(currentTime - drone.lastBeacon >= BEACON_INTERVAL){ FANETMessage beacon = network.createBeacon(drone); network.queueMessage(beacon); drone.lastBeacon = currentTime;// Process beacon immediately for simulation network.processBeacon(beacon, drones, NUM_DRONES);}// Send telemetry to ground stationif(currentTime - drone.lastTelemetry >= TELEMETRY_INTERVAL){ FANETMessage telemetry = network.createTelemetry(drone);// Find route to ground stationuint8_t nextHop = router.findNextHop(drone, GROUND_STATION_ID, drones, NUM_DRONES);if(nextHop !=0xFF){if(drone.id ==0){// Drone 0 sends directly to ground station network.processTelemetry(telemetry);}else{// Other drones relay through mesh Serial.printf("[DRONE-%d] Relaying telemetry via Drone-%d\n", drone.id, nextHop); telemetry.type = MSG_RELAY; network.queueMessage(telemetry);// Simulate successful relay network.processTelemetry(telemetry);}} drone.lastTelemetry = currentTime;}// Leader announces periodicallyif(drone.isLeader && currentTime - network.lastLeaderAnnounce >= HEARTBEAT_INTERVAL *2){ FANETMessage announce = network.createLeaderAnnounce(drone); network.queueMessage(announce); network.processLeaderAnnounce(announce, drones, NUM_DRONES);}// Check for low battery emergencyif(drone.telemetry.batteryLevel <15.0&& drone.state != STATE_RETURNING){ Serial.printf("[DRONE-%d] LOW BATTERY WARNING: %.1f%%\n", drone.id, drone.telemetry.batteryLevel); drone.state = STATE_RETURNING; FANETMessage emergency = network.createEmergency(drone,"LOW BATTERY RTH"); network.queueMessage(emergency); network.processEmergency(emergency);}}// ===========================================// NETWORK MANAGEMENT// ===========================================// Check leader status network.checkLeaderStatus(drones, NUM_DRONES);// Update swarm state network.updateSwarmState(drones, NUM_DRONES);// ===========================================// PERIODIC STATUS OUTPUT// ===========================================if(currentTime - lastStatusPrint >= STATUS_PRINT_INTERVAL){ Serial.println("\n========== FANET STATUS UPDATE ==========");// Print swarm summary Serial.printf("[SWARM] Mission Time: %lu sec\n", network.swarmState.missionTime); Serial.printf("[SWARM] Active Drones: %d/%d\n", network.swarmState.activeDrones, NUM_DRONES); Serial.printf("[SWARM] Leader: Drone-%d\n", network.currentLeaderId); Serial.printf("[SWARM] Centroid: (%.6f, %.6f)\n", network.swarmState.centroid.latitude, network.swarmState.centroid.longitude);if(network.swarmState.targetFound){ Serial.println("[SWARM] *** TARGET LOCATED ***");}// Print neighbor tables Serial.println("\n[MESH] Neighbor Tables:");for(int i =0; i < NUM_DRONES; i++){ Serial.printf(" Drone-%d neighbors: ", drones[i].id);if(drones[i].neighborCount ==0){ Serial.print("(none)");}else{for(int j =0; j < drones[i].neighborCount; j++){ Serial.printf("D%d(%.0fm) ", drones[i].neighbors[j].droneId, drones[i].neighbors[j].distance);}} Serial.println();}// Print formation formation.printFormationStatus(drones, NUM_DRONES); lastStatusPrint = currentTime;}// Small delay to prevent watchdog timeout delay(50);}
47.4.3 Lab Exercises
Exercise 1: Mesh Topology Analysis
Objective: Understand how neighbor discovery works in FANETs
Tasks:
Run the simulation and observe the neighbor tables printed every 5 seconds
Note which drones can communicate directly (neighbors) vs which require multi-hop relay
Modify COMM_RANGE_METERS to 500 meters and observe how the mesh topology changes
Answer: With reduced range, which drones become isolated? How does this affect telemetry delivery?
Expected Observation: Reducing communication range fragments the mesh network, requiring more relay hops or causing some drones to lose connectivity with the ground station.
Exercise 2: Leader Election Simulation
Objective: Explore distributed leader election in drone swarms
Tasks:
Find the LEADER_TIMEOUT constant and reduce it to 2000 ms
Observe what happens when leader elections occur more frequently
Modify the election criteria in processLeaderAnnounce() to prioritize:
Highest battery level instead of highest ID
Most neighbors (best connectivity)
Answer: What are the trade-offs between different leader election criteria?
Code Modification (change in processLeaderAnnounce):
Objective: Implement and test different routing strategies
Tasks:
Locate the findNextHop() function in the FANETRouter class
The current strategy routes toward Drone-0 (ground station relay)
Modify the routing to consider:
Link quality (signal strength + battery)
Hop count minimization
Load balancing across multiple paths
Test your modifications and compare telemetry delivery reliability
Challenge: Implement a “geographic routing” strategy that forwards messages toward the GPS coordinates of the ground station.
Exercise 4: Swarm Formation Control
Objective: Experiment with different swarm formations
Tasks:
Find the FormationType enum and getFormationPosition() function
The simulation defaults to FORM_SEARCH_PATTERN
Modify setup() to use different formations:
FORM_LINE - for corridor search
FORM_WEDGE - for leader-following
FORM_CIRCLE - for area encirclement
Observe how formation affects coverage and connectivity
Questions to Answer:
Which formation provides the best mesh connectivity?
Which formation covers the most area?
How does formation choice affect battery consumption?
Exercise 5: Emergency Response Protocol
Objective: Design robust emergency handling for UAV networks
Tasks:
Find the emergency message handling in processEmergency()
Currently, emergencies are only logged - enhance the response:
When a drone sends LOW BATTERY, have nearby drones adjust formation
Implement a “buddy system” where healthy drones escort returning drones
Add altitude collision avoidance for returning drones
Test by reducing initial battery levels to trigger emergencies sooner
Code Addition:
// In processEmergency() - add swarm responsevoid respondToEmergency(uint8_t emergDroneId, VirtualDrone drones[],int numDrones){// Find nearest healthy drone// Reassign as escort// Update formation to close gap}
47.4.4 Expected Learning Outcomes
After completing this lab, students should be able to:
Outcome
Demonstrated Skill
Mesh Networking
Understand how drones discover neighbors and form ad-hoc networks without infrastructure
Multi-Hop Routing
Explain how messages traverse multiple drone relays to reach the ground station
Leader Election
Implement and compare distributed coordinator selection algorithms
GPS Handling
Parse, validate, and share geographic coordinates between network nodes
Swarm Coordination
Design formation patterns that balance coverage, connectivity, and energy efficiency
Fault Tolerance
Build resilient systems that handle drone failures, low batteries, and link disruptions
Real-Time Telemetry
Aggregate and process sensor data from multiple mobile nodes
Show code
InlineKnowledgeCheck_kc_uav_lab_1 = ({question:"A package delivery company operates a fleet of 20 delivery drones in a city with a 10 km radius coverage area. FAA regulations require that all drones maintain communication with the ground control station and broadcast Remote ID (identification and location) every 1 second. Each drone sends 100 bytes of telemetry per second. What is the minimum uplink bandwidth required at the ground control station?",options: ["16 kbps (kilobits per second)","160 kbps (kilobits per second)","1.6 Mbps (megabits per second)","16 Mbps (megabits per second)" ],correctAnswer:0,feedback: {correct:"Perfect calculation! 16 kbps is correct. Data per drone per second: 100 bytes/s times 8 bits/byte = 800 bits/s per drone. Total for 20 drones: 20 drones times 800 bits/s = 16,000 bits/s = 16 kbps. Real-world systems add protocol overhead (headers, acknowledgments) increasing requirement to approximately 20-22 kbps - still well within typical 4G/5G cellular uplink capacity.",incorrect: ["If you calculated 160 kbps, check your unit conversion. 100 bytes/s times 20 drones = 2,000 bytes/s. Converting to bits: 2,000 times 8 = 16,000 bits/s = 16 kbps, not 160 kbps.","Calculating 1.6 Mbps suggests incorrect unit conversion or adding unrelated data streams. The calculation should be: 20 drones times 100 bytes/s = 2,000 bytes/s times 8 bits/byte = 16,000 bits/s = 16 kbps.","If you arrived at 16 Mbps, there's a major unit conversion error. You may have confused bytes with kilobytes. 100 bytes = 0.1 kilobytes, not 100 kilobytes." ] },difficulty:"easy",learningObjective:"Calculate bandwidth requirements for UAV telemetry and regulatory compliance data"})
Connecting to Real UAV Systems
The concepts demonstrated in this simulation directly map to real FANET implementations:
Simulation Concept
Real-World Implementation
FANETMessage struct
MAVLink protocol messages used by PX4/ArduPilot
GPSCoordinate handling
NMEA sentence parsing from GPS modules
Neighbor discovery
802.11s mesh networking or custom radio protocols
Leader election
Raft/Paxos consensus adapted for mobile networks
Multi-hop routing
AODV, OLSR, or geographic routing (GPSR, GOAFR)
Swarm formations
Distributed formation control algorithms (consensus-based)
For production UAV systems, consider using: - PX4 or ArduPilot for flight control - MAVLink for ground-to-air communication - ROS 2 for multi-robot coordination - ESP-NOW or LoRa for air-to-air mesh networking
Common Pitfalls
Setting beacon intervals too short (under 500 ms): Frequent beacons flood the network with broadcast traffic, consuming bandwidth and battery. In a 4-drone swarm with 200 ms beacons, each drone processes 15 messages per second, leaving little capacity for telemetry. The lab uses 2-second intervals as a practical balance between position freshness and network overhead.
Ignoring TTL limits on multi-hop messages: Without a TTL (Time-To-Live) field, relay messages can loop indefinitely between drones, creating broadcast storms. This lab caps relay at 5 hops. In real FANETs with 20+ drones, failing to decrement TTL can saturate the entire mesh within seconds.
Electing leaders based on drone ID instead of operational metrics: The simplest election algorithm picks the highest ID, but this ignores battery level, connectivity, and position. A drone with 10% battery elected as leader will force a re-election within minutes. Always factor in battery above 30% and neighbor count as selection criteria.
Assuming all drones are always in range of each other: With a 2 km communication range and drones spread over a search area, some drones will be out of direct range. Failing to implement proper multi-hop routing means those drones silently lose contact with the ground station. Always verify neighbor table completeness before sending critical telemetry.
Not accounting for GPS jitter in formation control: Consumer GPS has 2.5-meter accuracy. Formation algorithms that demand sub-meter precision will cause drones to oscillate continuously, wasting battery and creating collision risks. Use a dead-band threshold of at least 5 meters before issuing position corrections.
Worked Example: FANET Multi-Hop Relay Energy Analysis
Scenario: In the Wokwi lab simulation, Drone-3 must relay telemetry to the ground station through Drone-0. You need to calculate the energy cost of multi-hop relay compared to direct transmission.
Direct battery life: 26,640 J ÷ 2,282 J/day = 12 days
Result: Multi-hop relay extends Drone-3 lifetime 23× (from 12 to 280 days), but places burden on Drone-0. This is why FANET protocols implement fair relay scheduling and energy-aware routing.
Key Insight: Radio power scales exponentially with distance (inverse square law). A 2.5 km transmission uses 24× more energy than a 150m transmission for the same data rate. Multi-hop mesh networks distribute this cost across cooperative nodes.
Decision Framework: When to Use FANET vs Star Topology
Factor
Star (All→Ground Station)
FANET Mesh (Peer-to-Peer)
Swarm Size
1-8 drones
5-50 drones
Range to GCS
All within 2 km
Some >2 km from GCS
Energy Equality
Edge drones privileged (low relay load)
Distributed (fair relay scheduling)
Latency
Low (1 hop = 50-100 ms)
Variable (3 hops = 150-300 ms)
Resilience
Low (GCS failure = total loss)
High (mesh self-heals)
Complexity
Low (simple protocol)
High (routing + leader election)
Deployment Time
Fast (<30 seconds)
Moderate (2-3 minutes for mesh formation)
Best Use Case
Agricultural survey (small area, reliable GCS)
Search & rescue (no infrastructure, wide area)
Decision Rules:
Can ALL drones reach GCS directly at low power (<50 mA TX)? → Star
Is mission area >5 km² with GCS at edge? → FANET
Is infrastructure unreliable (disaster zone)? → FANET
Is swarm <8 drones AND GCS reliable? → Star
Is energy fairness critical (long mission, no recharge)? → FANET
Default for unknown conditions → FANET (more resilient)
Common Mistake: Not Accounting for Leader Election Overhead
The Mistake: A developer implements leader election with LEADER_TIMEOUT = 5 seconds and leader announcement every 1 second. They expect 5 leader messages per timeout period. In a 10-drone swarm, this causes 50 messages/cycle (10 leaders × 5 announcements).
Why It Fails: Frequent leader announcements consume bandwidth and energy: - Each announcement: 20 bytes overhead + 64 bytes data = 84 bytes - 10 drones × 84 bytes × 5 announcements/cycle = 4,200 bytes every 5 seconds - Over a 30-minute mission: 4,200 bytes × 360 cycles = 1.5 MB of leader traffic - At 1 Mbps data rate: 12 seconds of channel busy time (6.7% duty cycle)
The Fix: Tune leader announcement frequency to balance detection speed vs overhead:
Rule: Set LEADER_TIMEOUT to 3-4× the LEADER_ANNOUNCE_INTERVAL to tolerate 2-3 missed heartbeats before triggering election. This prevents false elections from temporary packet loss while keeping overhead <2%.
Interactive Quiz: Match Concepts
Interactive Quiz: Sequence the Steps
Label the Diagram
💻 Code Challenge
47.5 Summary
This hands-on lab demonstrated key FANET concepts through practical ESP32 simulation:
Mesh Networking: Drones discover neighbors via beacon broadcasts and maintain neighbor tables with distance and signal strength
Multi-Hop Routing: Messages traverse multiple relay nodes using geographic-based routing toward the ground station
Leader Election: Distributed algorithm selects swarm coordinator based on battery level and connectivity
Formation Control: Various patterns (line, wedge, circle, grid, search) balance coverage, connectivity, and energy efficiency
Emergency Handling: Low battery triggers return-to-home with mesh-relayed emergency alerts