Two questions landed on me this week, back to back: how does a ground control station work with no internet, and how does it talk to a real drone? They sound like the same question. Hindi pala (turns out they're not) — and untangling them is the whole design.
"How is this supposed to work with no internet?" "But then how does it talk back and forth to a real device?"
They sound like one question. They're actually opposite ends of the design, and the most important thing I can say is: air-gap means no internet — it does not mean no communication. A field laptop with no signal still flies its drones. Yan mismo yung buong punto (that's the entire point) of building it this way.
Two channels, and neither is the internet
There are two completely separate channels in this system. People collapse them into one ("the network") and then the air-gap claim sounds impossible.
INTERNET ✗ ─────── [ operator laptop ] ─────────────────┐
(never required) │ │
channel A: MAP │ channel B: DEVICE LINK
(local file) ▼ (local radio / serial / Wi-Fi)
basemap.pmtiles UDP · serial · RF · mesh · SATCOM → the asset
Channel A is the map. Tiles come from a single file that ships in the bundle, not from a tile server. You build or download that file once, with internet, commit it, and from then on the map renders offline forever. Build-time touches the network; runtime never does.
Channel B is the device link. This is the one people don't expect to exist on an "air-gapped" box. It's a local bearer — a point-to-point radio, a serial cable, a Wi-Fi access point the drone itself broadcasts, a mesh, a satellite modem. None of those is the public internet. A SiK radio on 915 MHz doesn't route through Cloudflare.
So: no internet, full comms. Hindi sila magkalaban (the two aren't in tension) once you stop treating them as the same wire.
The device bearers
Every supported asset talks over something local:
Asset
Bearer
Transport
Micro-UAS (Tello)
the drone's own Wi-Fi AP
UDP text protocol
PX4 / ArduPilot
SiK/RF radio, or a tactical LAN
serial, or UDP
STANAG 4586 UAV
military datalink
LAN / serial
Swarm
mesh radio
(planned MeshLink)
Long range
SATCOM
its own bearer
Notice there's no row that says "the cloud."
The implementation detail is behind the wall
The concept above is open. Where this lives in the code, the clean-room Tello adapter, and the device-swap mechanics need a password — same one as the other GCS posts, or request access below.
Where this lives in the code
Here's the rule that keeps "no internet" honest: the adapter owns the bearer, and nothing above it knows or cares what the bearer is. Open a socket, open a serial port, join a mesh — that decision lives in exactly one place per device and nowhere else.
That one place is the VehicleAdapter port. It has four things on it: connect, telemetry, send, disconnect. The command pipeline, the map, the recorder, the UI — all of them consume VehicleAdapter. None of them imports a protocol. The drone's wire format stops at the adapter and never leaks upward.
The connection details come from config, as a protocol-neutral descriptor — a scheme (udp, serial, …), an address, and a reference to a secret (never the secret itself). One deliberate choice there: that descriptor is threaded through the main process only. The UI never receives the wire address. The renderer has no business knowing how to reach the radio — it's the least-trusted part of the app, so it doesn't get to hold the keys to the link. Security as a default, not a bolt-on.
The part I actually built this week: a real device
Architecture is cheap until something real connects. So I wrote the first hardware adapter — a clean-room driver for the little Ryze/DJI Tello, over its published UDP text protocol. (Clean-room: re-authored from the public spec, no vendor SDK, no GPL source. Protocols are facts; the code is mine.)
One thing to be clear about: the Tello is the proof, not the plan. It's a $100 toy drone — I picked it precisely because it's cheap, has a documented protocol, and I can hold it in my hand to test against. It's the first device through the seam, not the device this is for. The real targets are PX4/ArduPilot over a SiK radio, STANAG 4586 datalinks, swarms over mesh — the serious platforms in that bearer table above. The whole reason the architecture is shaped this way is so the Tello and a tactical UAV are the same kind of thing to everything upstream. Kung kaya ng Tello, kaya ng iba (if the Tello works through the seam, so will the others) — that's the test the pattern has to pass, and the Tello is how I prove it cheaply.
It does exactly what the port says and nothing more:
connect — open two UDP channels on the drone's local Wi-Fi, send command to enter SDK mode, wait for ok.
telemetry — the drone broadcasts a state line ten times a second; parse it into the same normalized telemetry shape the simulator emits.
send — translate an operator Command into a wire string, write it, wait for the drone's ok/error.
The interesting bit is what it refuses. A Tello has no GPS. So when the operator issues an absolute "go to this coordinate," the adapter doesn't fake it — it rejects it, with a reason the operator can read. Same for return-to-home, same for arm/disarm (a Tello auto-arms on takeoff). The adapter's job is to translate the ideal command into the device's reality, and to be honest when the device can't comply. Silent no-ops are how you lose a drone.
And because the wire encoding is a pure function — command in, string out — I can test every one of those decisions with no drone and no network on the bench. The sockets are behind their own little seam too, so the tests inject a fake transport and drive the whole handshake in memory.
Swapping devices: the headline
The reason any of this matters is the question I got asked most: can we swap to a different device later? Oo — at sadyang boring (yes — and it's deliberately boring). There are two swap points:
Swap the device. A new platform is a new adapter package plus one line in a switch. Domain, pipeline, UI: untouched. The simulator and a real radio are the same VehicleAdapter — "sim ≡ real" stopped being a slogan the moment the Tello adapter passed the same tests the sim does.
Swap the bearer. Inside an adapter, UDP can become a serial-to-UDP bridge or a relay without touching the protocol logic above it. Useful in the field; essential for tests.
That's the payoff of putting the hexagon in early. The expensive decision (where's the seam?) was made on day one; adding a device is now cheap.
The link is still hostile (zero-trust on the wire)
One thing air-gap does not buy you: trust in the link. RF is broadcast. A Wi-Fi AP is joinable. Removing the internet removes the internet's threats, not the radio's. An isolated box on a hostile radio is still on a hostile radio.
So the model is zero-trust, applied to the device link: nothing is trusted just because it's already "inside." Air-gap is a perimeter, and zero-trust is the assumption that the perimeter means nothing — every session authenticates, every command is verified on its own merits, and being connected is never the same as being authorized. People reach for zero-trust to describe cloud networks; it maps just as cleanly onto a tactical radio link, kasi pareho lang ang assumption (because the assumption is the same) — the channel is hostile, so prove everything.
Concretely, that's the connection contract every real bearer has to satisfy, no matter how isolated the box is:
Mutual authentication — the station proves itself to the device and the device to the station. No one-way trust.
Message signing — every command is signed, so a spoofed packet on the same frequency can't pass as a real one.
Anti-replay — a captured-and-resent command is rejected. Recording a valid "land" and replaying it later doesn't work.
Fail-secure — when verification fails or the link drops, the system fails to the safe state, never the open one.
Audit-on-deny — every rejection is recorded, so the denial itself leaves a trail.
The simulator endpoints skip the crypto — they're local and trusted by definition. Real bearers won't get that pass.
That layer isn't fully built yet, and I'd rather say so than imply it is. The Tello adapter has the handshake and the request/ack duplex; the zero-trust pieces — signing, anti-replay, mutual auth — come next. Right now the contract is designed and the seam is in place; the crypto is the next sprint, not a shipped claim.
The shape of the answer to both questions turns out to be the same: draw the seam in the right place, and keep the internet out of the parts that don't need it. The map reads a local file. The drone talks over a local radio. Neither one calls home. And when the next device shows up, it plugs into the same hole the last one did.
Build log entry. Apache-2.0. The air-gap + comms design is written up in docs/architecture/04-air-gap-and-comms.md.