Swarm Bus
Every drone in a fleet broadcasts a 20-byte position and velocity beacon twice a second, and every node, drone and ground station alike, hears every other node. That is the whole contract, and the important part is what it does not need: no ground station in the path, no leader, no session, no negotiation. Power the ground station off and each drone’s neighbour table is unchanged.ados-swarmbus is the service. It is a Core service on both flight-capable
profiles (drone and ground_station), gated the same way ados-mavlink is,
because the two are the same seam seen from either end: a drone fills its beacon
from the router’s state socket, and a ground station listens so the operator’s
fleet view is local-first.
A ground station holds slot 0 and never emits a beacon. It is not an aircraft.
The beacon
20 bytes on the air. Position and velocity use the MAVLinkGLOBAL_POSITION_INT encodings verbatim (degrees times 1e7, decimetres, cm/s),
so filling a beacon from the flight controller’s fused state is a shift rather
than a lossy re-quantisation.
Heading is not carried because every reference implementation derives it from
velocity, and 2 bytes to restate information the velocity already holds is
2 bytes of airtime.
seq_ms deliberately carries no wall clock: two drones with
unsynchronised clocks must still agree on which of two beacons is newer, and a
65.5 second wrap is far longer than the staleness window.
Status bits
One byte packs five independent conditions plus the active mode-precedence level. Each bit is a separate condition and they are never blended: an armed drone with a bad GPS fix and an armed drone in emergency are different situations.Airtime
This is the number the design lives on, so here is the arithmetic rather than the assertion. One beacon on the air at MCS 0:The neighbour table
Three seconds rather than one because outages of about a second are normal in
real formation flight, and dropping a neighbour that is still there is worse
than carrying a slightly old one: the separation layer would stop avoiding an
aircraft that has not gone anywhere.
The 64-entry cap is not reachable by a legal fleet. It bounds the table against
a garbage or hostile slot flood, since the slot field is a byte and 255 distinct
values are expressible.
GET /api/swarm/neighbors
fleet_id, slot, a neighbors array, a slots table and
a counter block.
The two differ exactly when a drone is lost, which is the operator-facing fact
slots exists to carry. A slot present in slots but absent from neighbors is
an aircraft the fleet issued a slot to and has since stopped hearing.
Each neighbour row carries slot, device_id, seq_ms, lat, lon, alt_m,
vx_ms, vy_ms, vz_ms, heading_deg, armed, guided, emergency,
gps_ok, hero, mode_precedence, age_ms and rssi_dbm. The counter block
carries beacons_tx, beacons_rx, beacons_bad_magic, beacons_bad_tag,
beacons_stale_dropped and neighbors_now.
Two rules the shape follows throughout:
- A missing reading is
null, never a plausible number.rssi_dbmis null when the capture carried no signal field, anddevice_idis null when the slot cannot be joined to an identity. A fabricated-100 dBmwould render as a real value. - Derived fields are computed on the node, so every consumer agrees on them.
heading_degandage_msare emitted rather than left to the client.
200. When the bus has published nothing (an absent
socket, a profile that does not run it, a radio that has not come up) the body is
the degraded shape: an empty neighbour array, zeroed counters, and fleet_id and
slot as null. Null rather than the config defaults, because a reader cannot
know the fleet identity of a service that is not running, and reporting 1 and
0 would make an unprovisioned node indistinguishable from a correctly
provisioned fleet-1 node with no neighbours.
The publish socket comes up first and unconditionally, so a bus with no radio
still answers with an empty table and zeroed counters. That is distinguishable
from an absent service, which answers with a null fleet id.
Fleet addressing
The bus rides the fleet identity the radio plane already uses. Nothing new is invented for it, so a node cannot be addressed differently on the two planes./etc/drone.key. That
file is the only shared-content key on disk: the bind protocol delivers it
byte-for-byte to both the drone and the ground station. The wfb-ng tx.key and
rx.key are the two different halves of a crypto_box pair, so a symmetric key
derived from either diverges across the two rigs and every frame is silently
dropped at the far end.
One symmetric key for the whole fleet is the point: every drone must be able to
decrypt every other drone’s beacon, which is what makes the neighbour table
work with the ground station off. A fleet is one trust domain.
The autonomy layer
Theswarm: config block drives the onboard autonomy that consumes the
neighbour table. It is off by default.
The setpoint loop
The loop runs insideados-mavlink, at 10 Hz, against the last neighbour
payload, dead-reckoning each neighbour forward by its age plus the time since
that payload arrived. That predict-and-correct split is why a 2 Hz beacon can
drive a 10 Hz controller: the loop never sees a staircase, and the correction
arrives before the prediction has drifted.
It lives in the router because the setpoint has to leave through the process
that holds the FC link, the sequence counter and the writer. Putting it in the
bus would mean a second command path to the autopilot.
It commands the flight controller through
SET_POSITION_TARGET_GLOBAL_INT and adds no radio traffic of its own: every
input is already on the air for other reasons, so a 24-drone swarm costs exactly
the beacon bandwidth.
Precedence
Highest authority first: hard separation, operator direct command, formation, flocking, hold. What rides bits 5 to 7 of the beacon is the active level, not the commanded one. A drone whose separation layer has taken over readshard-separation on the
operator’s screen, never the mode somebody asked for. Mode-transition ambiguity,
an operator believing one mode governs a vehicle while another actually does, is
implicated in a long series of supervisory-control losses, so the active level is
broadcast by the aircraft itself and every reader derives it from the same byte.
A node with no autonomy layer running radiates 000 there and every reader
honestly decodes hold.
What the layer never does
- No leader election. The operator screen is the single authority by construction. Electing a leader among drones over a lossy broadcast invites a split brain, a leader that still hears its peers but has lost the operator, for no benefit.
- No failsafe of its own. ArduPilot’s
FS_GCS_ENABLEandFS_LONG_ACTNand the geofence are the backstop and act independently. - It stops rather than substitutes. When the swarm is disabled, the vehicle is disarmed, the flight controller is out of GUIDED, or the neighbour table has been empty for the 3 second staleness window, the loop emits nothing and the flight controller holds on its own terms. It never competes with the autopilot’s failsafes.