GSoC 2026: AP_SwarmMesh: Resilient MAVLink Ad-HOC Swarm Networking for ArduPilot
A framework for onboard swarm coordination within ArduPilot
Mentors: Nate Mailhot & Asif Khan | Timeframe: May → Sep 2026 | Project: GSoC
1. Introduction
Hi ArduPilot community! My name is Kwaku and I am currently pursuing a Masters degree in Mechanical Engineering at the University of Ottawa. Over the past year, I have been working on a project which provides an open-source swarming platform for aerial drones using off-the-shelf micro-drone hardware called ArduSwarm. ArduSwarm mates Bitcraze Crazyflie hardware with a custom version of ArduPilot to enable self-contained decentralized indoor swarming, with the end goal of using the platform to support my current research into decentralized indoor swarming algorithms.
Figure 1 - From left to right: ArduSwarm drone hardware, Experimental validation of critical ArduSwarm functionality.
In this time, I have become very familiar with the topic of swarming and peer-to-peer (P2P) networks in particular, and as such I’m excited to be working on this with the community!
2. Problem Statement
Demand is rising for full-fledged swarming support within open-source flight control software like ArduPilot. Currently, most swarming coordination is centralized, meaning communication is routed through a central Ground Control Station (GCS) (see swarming).
This kind of swarm backend is:
- Vulnerable to connection loss (single point of failure).
- Relies on external computation for coordination and/or advanced autonomy.
- Not easily scalable (2-10 vehicles).
To enable a robust, decentralized swarm backend, I propose adding a new library to ArduPilot, dubbed AP_SwarmMesh, which enables distributed peer state updates and peer state forwarding while also maintaining an onboard peer state table which is accessible and adaptable for future swarm implementations within ArduPilot.
3. Project Proposal
This project builds a scalable communication backend within ArduPilot for decentralized peer-to-peer networks for swarm coordination
Figure 2 - High-level overview of the AP_SwarmMesh software and hardware structure. Each vehicle connects to onboard radios through two UART ports (one for standard telemetry, one for P2P mesh). AP_SwarmMesh serializes a stream of peer data into MAVLink packets and then encapsules them in a peer routing header. These peer packets are broadcast to neighbors and are distributed throughout the swarm. Upon receiving a peer packet, a vehicle parses the packet and stores a limited state of each peer.
The project introduces:
- Logging integration: any peer messages which pass the initial screening are parsed and are logged using the existing logging backend.
- Peer state table: the user sets a swarm size which initializes a peer state table in memory. As peer messages come in, the corresponding state entry in the peer table is updated.
- Persistent snapshots: a new directory under external storage,
APM/PEERS/, which stores snapshots of the peer state table at a set frequency. Intended to protect against reboots. - Peer stream: user-configurable stream of MAVLink messages intended to disseminate state data to peers. Rate-limited depending on hardware backend.
- Peer frame header: peer header prepended to MAVLink messages which includes important routing information used for forwarding and duplicate/timeout rejection.
This project builds infrastructure to enable full decentralized swarming within ArduPilot. Rather than managing multiple vehicles through a GCS with multiple vehicle connections, this project gives each drone the ability to manage swarm coordination locally, while also enabling MAVLink forwarding over an ad-hoc peer network in environments where network connections are unreliable.
4. Wire Format
The following byte map details the peer header, which is prepended to each outgoing peer packet. The peer header is fixed in length (23 bytes) and thus can be read quickly first by a state machine which routes the packet and filters duplicate / old packets. Only after passing through the state machine will a packet’s contents be parsed by passing the payload to a MAVLink parser. This minimizes the resources used in the receiving path.
Table 1 - Proposed Peer Routing Packet Structure
| Name | Data Type | Purpose |
|---|---|---|
magic/sync |
uint16_t |
Marks the start of a peer packet. |
version |
uint8_t |
Lets us change the header later without breaking compatibility. If we later add fields, change flag meanings, etc. old implementations can reject unsupported versions cleanly. |
flags |
uint8_t |
Bitmask for special behaviors. It tells the receiver how to interpret the packet beyond the basic fields. |
type |
uint8_t |
Payload contents (ie. MAVLink, ack, etc.). Allows us to route packets without parsing the payload. |
ttl |
uint8_t |
Time-to-live, essentially a hop count limit. Every relay decrements it by 1. When it reaches 0, the packet is dropped. Prevents: loops, endless forwarding, stale packets. |
origin_id |
uint8_t |
This identifies the node that originally created the packet. Adds: duplicates handling, stats, route/debug logging, source ID without parsing MAVLink sysid. |
prev_id |
uint8_t |
Tracks the most recent forwarding node. |
dest_id |
uint8_t |
Allows broadcast or targeted delivery. |
seq |
uint16_t |
Relay sequence number generated by the origin node. Used to identify each packet from each origin. Adds: duplicate suppression, packet tracking, ACK/NACK logic, loss stats. |
origin_time_us |
uint64_t |
Timestamp when the origin node created the relay packet (unix time). Available for GPS-equipped drones for synchronization. |
deadline_ms |
uint16_t |
This is a relative freshness budget. Tells the network how long the packet remains useful. (ex. a RC_CHANNELS_OVERRIDE packet is not very useful after 100 ms, whereas a STATUSTEXT is valid way after). |
payload_len |
uint8_t |
This is the length of the MAVLink frame. It tells the receiver where the payload ends. |
crc |
uint8_t |
One byte CRC over header contents (stx through payload_len) to catch errors in header. Payload has its own CRC. |
MAVLink Frame |
variable |
Carries the full serialized MAVLink packet unchanged. |
5. TX Path - Outbound
AP_Scheduler passes AP_SwarmMesh::update() at a reduced frequency of around 100 Hz to reduce compute overhead. If a serial port is bound to AP_SwarmMesh, it passes AP_SwarmMesh_Serial::update(), which iterates through the MAVLink streams (POSITION, ATTITUDE, STATUS, etc.), which are enabled by bitmask parameter. When a stream’s interval elapses, it checks for any priority deferred messages (HEARTBEAT / PARAM_SET) first, then falls through to stream messages serialised with the existing MAVLink macros.
Before sending the packet out, txspace() is checked for adequate space (packet is discarded if full). The P2P header is then prepended, and the packet is written as two succussive buffers to uart, which is the bound serial port which is connected to the P2P radio (either a companion computer in a ‘Full’ peer stream, or a generic radio in a ‘Lite’ peer stream). Also, forwarded packets are written to uart if there is space in txspace().
Any dropped packets are counted, and the loop yields if the scheduler’s 5ms budget is exceeded.
proposed_transmission_path.pdf (31.3 KB)
Figure 3 - Proposed transmission path.
6. RX Path - Inbound
Bytes are fed from the serial port (from either a companion computer in a ‘Full’ peer stream, or a generic radio in a ‘Lite’ peer stream) into a fixed-size peer header state machine. Bytes are fed into the state machine until the magic bytes are seen. The state machine then validates the header via a one-byte CRC and then reads the payload. Once the entire packet has been written to buffer, the state machine returns true.
A valid header then hits a version check which checks which header version is being sent. We then check if the packet is a duplicate against a per-peer ring buffer (any duplicates are discarded). Next, the packet passes a flag check (which checks for any special behavior such as if the origin drone had GPS-based RTC for synchronization). The packet is also checked for staleness, if the time since creation is greater than the deadline, the packet is dropped. Then, packets decrement TTL (zero-TTL packets are dropped).
If ttl > 1 and the destination id is not the same as the current sysid, a forwarded copy is written out (with prev_id rewritten to sysid and TTL decremented).
Finally, the packet hits a type check which routes the packet to the correct parser. If the packet is MAVLink, the MAVLink frame is fed to handle_mavlink() [5], and on a clean parse AP_Logger is written [6], and the periodic peer state snapshot is updated (at a set frequency).
proposed_receiving_path.pdf (43.3 KB)
Figure 4 - Proposed receiving path.
7. Peer State Table
The peer state table encodes the minimum data required for swarm coordination and management. The idea is that each drone within the swarm maintains its own table for each connected peer in memory so that it can be used in future swarming algorithms easily. The below struct is a basic v1 version which is subject to change given different swarming requirements. Each peer state table is around 160 bytes in memory; thus the maximum swarm size is set to 16 currently (~2.5kB in memory).
Peer states are updated as messages come in and are logged using the logging backend, similar to ADS-B. Table snapshots are saved to external storage periodically to protect against reboots.
struct PeerState {
// Peer identity
uint8_t sysid; // unique ID of original peer
uint8_t vehicle_type; // 0: copter, 1: plane, 2: sub, 3: blimp, 4: rover
uint8_t prev_id; // ID of peer which forwarded message
// Liveness / Link quality
uint64_t last_heard; // system time of last update from this peer for staleness detection (unix)
uint16_t last_seq; // for dedup ring buffer
uint32_t seq_seen_mask; // bitmask of the 32 seq numbers behind last_seq
uint8_t rssi; // signal strength
uint16_t rx_count; // received message count
uint16_t drop_count; // dropped message count
bool freshness; // true: FRESH, false: STALE
// Kinematic state
Vector3f local_pos_NED; // offset from origin [x, y, z] in meters
Vector3f global_pos; // GPS [lat (degE7), lon (degE7). alt (mm)]
float pos_covariance[9];
Vector3f attitude; // [pitch, roll, yaw] in rads
float att_covariance[9];
// Vehicle state
uint8_t mode;
bool armed_state; // true: armed, false: disarmed
bool landed_state; // true: landed, false: not landed
uint8_t failsafe_flags;
uint16_t battery_voltage;
uint8_t health_flags;
// Coordination state
uint8_t role;
uint8_t task_id;
uint8_t formation_slot;
Vector3f target_pos; // [lat (degE7), lon (degE7). alt (mm)]
uint8_t priority;
};
8. SITL and Hardware Demos
I am planning on completing an initial prototype for AP_SwarmMesh within the next couple of weeks. For the midterm evaluation, I would like to have a working demo of AP_SwarmMesh on SITL.
The SITL backend (AP_SwarmMesh_SITL) will replace the physical serial connection with a UDP socket. Each simulated vehicle binds to a known port and sends/receives the same p2p_header_t framed packets in a loop. This means:
- TX: instead of
uart->write(), serialize the header + MAVLink payload into a buffer andsendto()each peer’s UDP port (or a multicast address). - RX:
recvfrom()inupdate()feeds bytes into the sameparse_byte()state machine. No changes needed to the parsing or forwarding logic.
TTL and forwarding still work as-is, so we can test multi-hop routing by running 3+ SITL instances and blocking direct links by not sending to certain ports.
The key advantage is that the entire framing/routing stack (parse_byte, process_packet, forward_mavlink) is shared and tested in SITL before testing the same code on real hardware.
After SITL testing, I would like to try implementing a hardware demo showcasing a leader-follower formation flight using AP_SwarmMesh as the inter-vehicle communication layer.
A small fleet of ArduCopter drones would be powered on and each vehicle would initialize its peer state table by exchanging MAVLink heartbeat and position messages over the mesh network until every node has a complete view of the swarm. Once the tables are populated, an onboard Lua script reads the local peer state and computes each drone’s target position as a fixed NED offset from the designated leader.
The leader drone would hover in GUIDED mode and would be flown manually via RC_CHANNELS_OVERRIDE. As it moves, AP_SwarmMesh propagates its updated GLOBAL_POSITION_INT to all followers, where the Lua script continuously recomputes the offset target and issues SET_POSITION_TARGET_GLOBAL_INT commands to maintain formation.
The demo would showcase the full library stack: serial framing, header CRC, deduplication, TTL-based multi-hop forwarding, peer state updates, and the onboard logger.
9. Request for Feedback
It would be great to get some feedback from the community! Specifically, I would appreciate any feedback regarding:
- Architectural issues: state machine logic, peer state table contents, external logging structure, etc.
- P2P Mesh use-cases: what specific use-case do you have in mind for a P2P network? Are there specific MAVLink messages that you would need? Specific routing / filtering algorithms you would want?
10. GitHub
My working repository is below. Feel free to observe my progress as I go!
Repository: https://github.com/kwakurichter/ardupilot_gsoc/tree/feature/AP_SwarmMesh
I am primarily using the AP_Beacon library as a reference for my library structure:
AP_Beacon: https://github.com/ArduPilot/ardupilot/tree/master/libraries/AP_Beacon
11. References
[1] - https://summerofcode.withgoogle.com/programs/2026/projects/kTHHO1JP
[2] - https://github.com/kwakurichter/ArduSwarm
[3] - https://ardupilot.org/planner/docs/swarming.html
[4] - https://ardupilot.org/mavproxy/docs/modules/swarm.html
[7] - https://ardupilot.org/copter/docs/common-ads-b-receiver.html

. 
