/*
* ============================================================================
* 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 Protocol
enum 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 States
enum DroneState {
STATE_INITIALIZING,
STATE_SEARCHING,
STATE_HOVERING,
STATE_RETURNING,
STATE_RELAYING,
STATE_EMERGENCY,
STATE_LEADER
};
// Swarm Formation Types
enum FormationType {
FORM_LINE,
FORM_WEDGE,
FORM_CIRCLE,
FORM_GRID,
FORM_SEARCH_PATTERN
};
// GPS Coordinates Structure
struct GPSCoordinate {
double latitude;
double longitude;
float altitude; // meters
float accuracy; // meters (GPS precision)
unsigned long timestamp;
bool valid;
};
// Neighbor Information
struct Neighbor {
uint8_t droneId;
GPSCoordinate position;
float signalStrength; // dBm
float distance; // meters
unsigned long lastSeen;
bool isAlive;
DroneState state;
float batteryLevel;
};
// Network Message Structure
struct FANETMessage {
uint8_t sourceId;
uint8_t destId;
MessageType type;
uint8_t hopCount;
uint8_t ttl; // Time to live (max hops)
unsigned long timestamp;
uint8_t dataLength;
uint8_t data[64];
};
// Drone Telemetry Data
struct DroneTelemetry {
uint8_t droneId;
GPSCoordinate position;
float batteryLevel; // 0-100%
float batteryVoltage; // Volts
DroneState state;
float heading; // degrees
float speed; // m/s
float verticalSpeed; // m/s
uint8_t neighborCount;
uint8_t signalQuality; // 0-100%
bool hasTarget; // Search target detected
float targetBearing; // degrees to target
};
// Swarm State
struct SwarmState {
uint8_t leaderId;
FormationType formation;
GPSCoordinate centroid; // Swarm center
uint8_t activeDrones;
float coverageArea; // km squared
unsigned long 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;
// Timing
unsigned long lastBeacon;
unsigned long lastHeartbeat;
unsigned long lastTelemetry;
unsigned long lastSwarmUpdate;
unsigned long lastLeaderHeartbeat;
// Formation position
int formationIndex;
GPSCoordinate targetPosition;
// Mission state
float 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 target
double 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 drone
float distanceTo(GPSCoordinate& other) {
// Haversine formula for GPS distance
double R = 6371000; // Earth radius in meters
double 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 difference
float altDiff = other.altitude - position.altitude;
return sqrt(horizontalDist * horizontalDist + altDiff * altDiff);
}
// Check if another position is within communication range
bool isInRange(GPSCoordinate& other) {
return distanceTo(other) <= COMM_RANGE_METERS;
}
// Update neighbor information
void updateNeighbor(uint8_t neighborId, GPSCoordinate& pos,
DroneState neighborState, float battery) {
// Check if already in neighbor list
for (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 neighbor
if (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 neighbors
void pruneNeighbors() {
unsigned long 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 array
for (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 progress
if (!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;
unsigned long 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 transmission
bool queueMessage(FANETMessage& msg) {
if (queueCount >= MESSAGE_QUEUE_SIZE) {
return false;
}
messageQueue[queueTail] = msg;
queueTail = (queueTail + 1) % MESSAGE_QUEUE_SIZE;
queueCount++;
return true;
}
// Dequeue next message
bool dequeueMessage(FANETMessage& msg) {
if (queueCount == 0) {
return false;
}
msg = messageQueue[queueHead];
queueHead = (queueHead + 1) % MESSAGE_QUEUE_SIZE;
queueCount--;
return true;
}
// 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, const char* 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 message
void 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 tables
void 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 tables
for (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 station
void 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 message
void 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 announcement
void 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 wins
if (announcedLeader > currentLeaderId ||
(currentLeaderId == 0xFF && leaderBattery > 30)) {
currentLeaderId = announcedLeader;
lastLeaderAnnounce = millis();
swarmState.leaderId = currentLeaderId;
// Update drone states
for (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 forwarding
void 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 destination
if (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 election
void checkLeaderStatus(VirtualDrone drones[], int numDrones) {
unsigned long 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 candidate
uint8_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 metrics
void 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 radians
float 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 leader
if (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 heading
float 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 status
void 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++) {
const char* 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 destination
uint8_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 0
float 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 drone
return 0;
}
// Find neighbor closest to drone 0
for (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 check
for (int i = 0; i < source.neighborCount; i++) {
if (source.neighbors[i].droneId == destId) {
return destId;
}
}
// No route found
return 0xFF;
}
// Calculate route quality metric
float calculateLinkQuality(Neighbor& neighbor) {
// Combine signal strength and battery level
float signalFactor = (neighbor.signalStrength + 100) / 50.0; // Normalize
float 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;
// Timing
unsigned long lastStatusPrint = 0;
unsigned long simulationStart = 0;
const unsigned long 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() {
unsigned long 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 beacon
if (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 station
if (currentTime - drone.lastTelemetry >= TELEMETRY_INTERVAL) {
FANETMessage telemetry = network.createTelemetry(drone);
// Find route to ground station
uint8_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 periodically
if (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 emergency
if (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);
}