Set Up a Ground Station
This is the whole setup arc in the order an operator does it. Work down the page; stop when you have what you need.1
Install the agent
Install the agent on the flashed SBC.
2
Reach the node
Reach the node over the AP, USB, or the LAN.
3
Pick a role
Pick a role: direct, relay, or receiver.
4
Run the setup wizard
Run the setup wizard through to finalization.
5
Pair with Mission Control
Pair with Mission Control, or stay local-only.
6
Pair the air link
Pair the air link to the drone.
7
Build a mesh (optional)
Bring up a receiver, then add a relay, pairing them with field pairing on the OLED.
Before you start
- A flashed SBC with a supported board. See Supported hardware.
- One RTL8812EU USB Wi-Fi adapter for the air link.
- A second USB Wi-Fi adapter if this node will be a relay or a receiver. It does not have to be an RTL8812EU; any adapter that supports 802.11s or IBSS works.
- Antennas: a 5 GHz omni on UNII-3 (channel 149 or 153) for the drone link, and a 2.4 GHz omni on the mesh radio (channel 1 by default). Keep at least 30 cm between antennas on the same chassis.
- The pairing key from the drone you want to receive from.
- Power, case and cabling. See Power and runtime.
Install the agent
Every documented install is the curl bootstrap. There is no localinstall.sh
to run unless you cloned the repo yourself.
--upgrade. Full flag surface
and the flash-tool alternative are on Installation.
On the ground-station profile the install also:
- Pulls the mesh dependencies (
batctl,avahi-daemon,wpasupplicant, and an 802.11s SAE backend). No flag is required; the profile always pulls them. - Scans for a second USB-attached Wi-Fi adapter and writes
mesh_capable: trueto/etc/ados/profile.confwhen it finds one. - Creates
/etc/ados/mesh/so the role-transition flow has somewhere to write identity files. - Lays down the three role-gated units, all masked and inactive until the role sentinel says otherwise:
The single-node RX unit,
ados-wfb-rx, is gated to the direct role so it
never grabs the adapter a relay or receiver unit drives.
Reach the node
Three ways in. All of them reach the agent on port8080.
The AP broadcasts as
ADOS-GS-<short-id> and hands out DHCP leases from
192.168.4.10 to 192.168.4.100. Joining it on a phone usually raises the
operating system’s captive-portal prompt, because the agent answers the standard
Android, iOS, macOS, Windows and Samsung connectivity probes with the expected
responses. If the prompt does not appear, open http://192.168.4.1:8080
directly.
The captive responder runs only during the first-time setup phase. Once setup is
finalized it deactivates, so it does not re-trigger on every reconnect.
0600 at
/etc/ados/ap-passphrase. Read it on the box:
<hostname> is the node’s system hostname, which is what avahi publishes as an
A-record. Set it with --name at install time, or read it with hostname. A
constructed name is not published and will not resolve.
For USB, plug a USB-C cable from your laptop to the node’s OTG port: the node
brings up usb0 at 192.168.7.1/24 and a single-host DHCP server hands your
laptop 192.168.7.2. See USB tether and
Wi-Fi AP mode.
Pick a role
Every ground station runs in one of three roles. The role is one config key,ground_station.role, and it decides which services start and what the OLED,
the CLI and Mission Control show.
direct
Single-node receive. The default. No mesh services.
relay
Forwards the drone’s fragments to a receiver over a local mesh.
receiver
The hub. Combines fragments from itself and every paired relay, and
publishes the clean stream.
Pick
direct when you have one ground station, the flight area is small,
and one ground point sees everything. Hardware: the SBC, one RTL8812EU, antennas
and the optional front panel.
Pick relay to extend the receive footprint across an obstructed area:
behind a hill, across a field, on the far side of a building. A relay listens to
the drone with its own RTL8812EU and forwards the fragments it heard over the
mesh instead of decoding locally. A receiver must already exist in the
deployment; a fresh box cannot become a relay until it has paired with one and
received a mesh invite.
Pick receiver for the node where the pilot and Mission Control sit. The
receiver accepts fragments from two sources at once, its own radio and the UDP
forwards from every paired relay, and runs the Reed-Solomon FEC combine across
the merged stream. If a packet is missed locally but heard by a relay, the
receiver recovers it. There is exactly one receiver per deployment.
Mesh capability gating
Setting the role config is not enough. A node will not bring mesh services up unless it is mesh-capable, which requires:- A second USB-attached Wi-Fi adapter present at runtime. The detector walks
/sys/class/net/wlan*and checks each interface is behind a USB bus, so an onboard SDIO or PCIe radio is not mistaken for the mesh carrier. A Pi 4B’s built-in Wi-Fi does not count. - The mesh dependencies installed on the host. The ground-station install path pulls them by default.
mesh_capable=true, Mission Control hides the Distributed RX and Mesh
surfaces, the OLED Mesh submenu disappears, and a role change to relay or
receiver returns 409 E_MESH_NOT_CAPABLE.
Run the setup wizard
Open the reach URL and work down the step list. On a ground station with a cloud posture there are eleven steps; local-only hides the Mission Control pairing step, leaving ten. A step marked optional never blocks the finish.1
Welcome
Confirms the device name, board and version, and shows a chip row for the
local network (Wi-Fi, hotspot, USB tether or LAN). If no usable local
network is up, this step asks you to bring one up before continuing.
2
Profile
Pick Ground station, then pick the role from the table above. The
recommended role appears as a hint based on the hardware fingerprint.
3
Operating region
Optional, and never blocks the wizard. The agent defaults to an unrestricted
RF posture. Pin a region here if you want your local jurisdiction’s channel
and power limits enforced. While unpinned, local RF compliance is your
responsibility.
4
Hardware check
Sweeps every component the chosen role requires: SBC, RTL8812EU, kiosk
display, gamepad, and the second adapter for mesh roles. Resolve anything
reported
missing before continuing; optional items can be skipped.5
Cloud posture
ADOS is local-first, so the default is local-only: Mission Control
reaches the agent over the LAN by hostname or IP and stores the key locally,
with no cloud round-trip. Pick cloud or a self-hosted backend only when you
need cross-network access. Local-only is a correct, fully working state.
6
Pair with Mission Control
Shown only when you chose a cloud or self-hosted posture. See
Pair with Mission Control.
7
Video
Confirms WHEP video is live. On a ground station that means the received
stream is being republished. Skippable if you do not need video on this node.
8
Ground receiver
Configure the WFB receiver and the mesh role, then bind to a drone. This is
the air link, separate from Mission Control pairing. See
Pair the air link.
9
Local display
Offered when a kiosk display is attached: driver install plus calibration. A
touch screen prompts a four-corner calibration.
10
Remote access
Optional. Configure a tunnel for reaching the node from outside the local
network. Skip it for an offline or LAN-only setup.
11
Finish
Marks setup finalized, which is the flag the webapp uses to gate the rest of
its surface, and hands you off to Mission Control. The sidebar then shows the
role-aware entries and page visibility follows the profile.
Pair with Mission Control
Local-only (the default): pair from Mission Control’s add-a-node card using the ground station’s hostname or IP over the LAN. No code is needed. Cloud or self-hosted: the agent shows a six-character pairing code. Enter it in Mission Control to accept the device, or accept a code issued by Mission Control. See Connecting and Pairing.Pair the air link
This is the WFB-ng key exchange between the drone and the ground station. It is a different thing from Mission Control pairing, and you need both. On the wizard’s ground-receiver step (or Mission Control’s Radio tab), paste the drone’s pairing key, pick the radio channel, and pair. The agent stores the key, configures the RX service, and starts receiving. Live RSSI and bitrate appear once the air side is in range. After pairing:- This ground station receives video only from the paired drone.
- Another ground station cannot intercept the stream.
- The pairing survives reboots and power cycles.
- You pair once, unless you factory-reset either side.
Bring up a receiver
Do the receiver first. A relay cannot join a deployment that has no hub. Unlike a relay, a receiver needs no prior pairing. A fresh box comes up cold and generates its own mesh identity on first start. Set the role from the OLED (Mesh, Set role,receiver), from the setup webapp
(Ground Station, Role, Receiver), or over REST:
ados-batman and ados-wfb-receiver, writes receiver to
/etc/ados/mesh/role, clears the stale mesh and WFB runtime state files, and
starts the new units in order. ados-batman always comes up first, because the
WFB side binds to the batman-adv interface. The whole thing takes a couple of
seconds.
ados-wfb-rx, the single-node RX unit, is gated by the supervisor to the
direct role, so it does not start here.
What then runs on the box:
The receiver is also the mDNS publisher on the mesh: it advertises
_ados-receiver._tcp on bat0 so relays resolve it with no manual
configuration.
FEC combine
The Reed-Solomon FEC combine runs at the receiver. The radio defaults arefec_k = 8 data fragments per block and fec_n = 12 total, so 4 parity. The
receiver recovers the original block from any 8 of the 12 fragments, whichever
radio saw which fragment.
Cloud uplink election
The receiver runs batman-adv as a gateway client. If any node on the mesh advertises a cloud gateway (because it has a modem, Ethernet or a Wi-Fi client connection), the receiver picks the best one by TQ and routes cloud-bound traffic over it. When a gateway dies, batman-adv evicts it and the receiver picks the next best. No operator action needed. Pin one from the Mesh tab, or over REST:{"mode": "auto"} to release the pin. See
Uplink matrix.
Add a relay
A relay needs to know which receiver to forward to, and it learns that during pairing. So the order is: pair first, then transition the role. Pair the relay to the receiver using field pairing on the OLED, then transition:relay) or the setup webapp (Ground Station,
Role, Relay).
Same shape as the receiver transition: the previous role’s units stop, all three
mesh units are masked, then ados-batman and ados-wfb-relay are unmasked,
relay is written to /etc/ados/mesh/role, the stale runtime state files are
cleared, and the new units start with ados-batman first.
A relay forwards fragments rather than decoding them locally, so the FEC combine
and the clean stream belong to the receiver. Point Mission Control and your
browser at the receiver for video, not at a relay.
Verify the relay
receiver_ip, receiver_port, fragments_seen, fragments_forwarded
and up: true. If up is still false after a few seconds, the relay has not
resolved the receiver over mDNS: check the receiver is up and reachable on the
mesh.
On the setup webapp’s Ground Station page the role state should read
current: relay, configured: relay, mesh_capable: true, and the Mesh
section should show the receiver with a non-zero TQ.
Going back to direct
From the OLED (Mesh, Leave mesh) or the setup webapp (Ground Station, Role, Direct). The transition stops the relay units, masks all three mesh units, writesdirect to /etc/ados/mesh/role, and clears the stale runtime
snapshots so a freshly-direct node cannot serve old mesh data. The supervisor’s
direct role gate then brings ados-wfb-rx back up.
The mesh identity files stay on disk, so a later switch back to relay resumes
without re-pairing. To wipe the identity entirely, factory-reset.
Field pairing on the OLED
A field crew can drop a relay onto a deployment with no laptop, no phone app and no cloud service. The whole flow runs on the OLED and the four buttons of both nodes.1
On the receiver: open the Accept window
B3 to enter the menu, then Mesh, Accept relay. The screen flips to a
60 second countdown and an empty pending list. The receiver binds a UDP
listener on
bat0 port 5801.2
On the relay: send a join request
B3 to enter the menu, then Mesh, Join mesh. The relay scans for
receivers over mDNS on
bat0 and shows what it found. If nothing shows, the
receiver is out of reach or its Accept window is not open. Press B1 to send
the join request.3
On the receiver: approve
The pending list shows the relay’s device id. Highlight it with B2 and B3 if
more than one is pending, then press B1 to approve.
4
On the relay: it writes to disk
The relay decrypts the invite bundle with its ephemeral key and writes
/etc/ados/mesh/id, /etc/ados/mesh/psk.key (mode 0600),
/etc/ados/mesh/receiver.json and the drone-paired WFB receive key. The
OLED flips to the Joined Status screen with the mesh id, the receiver host
and the live fragment count.direct to relay before the
mesh services start. See Add a relay.
The same flow is available over REST when you do have a laptop:
The protocol
The request is
{"type": "join", "device_id": "<id>", "pubkey_hex": "<32 bytes hex>"}.
The relay generates a fresh ephemeral keypair for every attempt and discards it
once it has decrypted the reply.
The reply is one binary blob: 32 bytes of receiver ephemeral public key, then a
12-byte nonce, then the ciphertext and its 16-byte authentication tag. The
plaintext is a JSON object carrying the mesh id, the mesh PSK, the drone
channel, the WFB receive key, the receiver’s mDNS host and port, and
issued_at_ms plus expires_at_ms.
The receiver sends the blob twice, 100 ms apart, because UDP on a lossy mesh
drops packets and a single loss should not force the operator to redo the flow.
The relay uses the first copy and tears down its keypair, so the second fails to
decrypt and is dropped silently.
A bundle whose expires_at_ms has already passed is dropped and the relay falls
back to a re-request.
Security model
The threat model is “an operator with physical access to both nodes for one minute”. Out of scope: hostile actors on the same channel, capture-and-replay against historical pairings, side channels. In scope:- Mutual authentication via ECDH. The encrypted reply only opens with the relay’s ephemeral private key, so a passive listener holding the same SSID and PSK cannot read the bundle.
- Authenticated encryption. ChaCha20-Poly1305 detects any tampering.
- Bundle expiry. 120 seconds. A captured bundle replayed later is rejected.
- Revocation. Revoke a relay’s device id from Mission Control or the setup
webapp. Revoked ids persist in
/etc/ados/mesh/revocations.json(mode0600), and a future join request from that device is dropped before it reaches the approval queue.
Verify
On a working deployment, power on the drone and the ground station and video and
telemetry appear within about 5 to 10 seconds of both sides being up. No setup
steps are needed on later flights.
If a relay does not see the receiver, go to
Mesh troubleshooting.
Re-run setup and reset
Re-run the wizard
From Settings, Profile, Re-run setup, or:Factory reset
Factory reset clears the pairing keys, the network config and all settings.- OLED: hold B4 for 10 seconds, then confirm by holding B4 again for 3 seconds.
- Setup webapp: Advanced, Factory Reset.
- Mission Control: the node panel’s Physical UI tab, Factory Reset. Requires a typed confirmation.
- REST:
POST /api/v1/ground-station/factory-reset. Requires a fingerprint confirmation token, so a stray request cannot wipe the node.
direct (stopping and masking
the mesh services), clears the WFB drone pair and the AP passphrase, then wipes
/etc/ados/mesh/id, psk.key, receiver.json, revocations.json and role.
The node reboots into the first-boot state and can be paired into a different
deployment.