Hardware & integrations

Hardware that talks back.

Atlas rides anything that carries UDP — but the interesting integrations go further: small companion processes own a specific radio, split its byte stream into a clean data channel and an out-of-band telemetry channel, and feed both to the daemon. The scheduler ends up knowing what the radio knows.

Field-proven adapter fleetAny hardware family can have one
The adapter contract

One stream in. Two channels out.

An adapter is deliberately small and deliberately separate: its own process, its own systemd unit, its own failure domain. The daemon never depends on it — telemetry loss can never back-pressure a data path.

Data channel

For in-band radios (serial modems), the adapter owns the port and exposes a clean PTY that pppd attaches to — the radio becomes an ordinary IP link with the vendor’s in-band chatter already stripped out.

Telemetry channel

Newline-JSON events into the daemon’s Unix socket, keyed by the same link_name as the [[link]] entry: RSSI local/remote, noise floor, TX-buffer fill, error counters, free-form extras. This is what powers signal-trend prediction and the dashboard’s radio columns.

Naming discipline

atlas-X = an adapter in the data path; atlas-telemetry-X = a poll-only feeder beside it. All share one runtime contract: bounded queues (drop-on-full), a reconnecting socket pusher, per-second rate derivation with counter-wrap detection.

The shape of it

One radio in, two channels out.

An adapter is a separate process with exactly one job: take whatever a radio speaks and hand Atlas two ordinary things — an IP link and a JSON feed. The daemon never learns the vendor protocol, so a new radio family is a new adapter and nothing else changes.

Radio → adapter → daemon the hardwareour processes
How an adapter connects a serial radio to the Atlas daemon An RF modem sends serial bytes to an adapter process. Inside the adapter a strict-filter parser splits the stream into two channels: DATA, a pseudo-terminal carrying raw bytes that PPP attaches to, and TELEMETRY, newline-JSON on a Unix socket. The data channel becomes an ordinary underlay link for the multi-path scheduler; the telemetry channel feeds the dashboard, the CLI and the REST API. RF MODEM RFD900x · SiK · LTE Iridium · Starlink /dev/rfd serial bytes data + RADIO_STATUS interleaved ADAPTER atlas-telemetry-sik-adapter strict-filter parser magic · length · msg-id · CRC — all four DATA PTY · raw bytes opaque to atlas /dev/atlas-sik-rfd PPP attaches here TELEMETRY UDS · JSON RSSI · noise · txbuf RADIO_STATUS parsed from in-band frames PTY → PPP → IP underlay link atlas binds a socket to that interface JSON over UDS /run/atlas/telemetry.sock ATLAS multi-path daemon multi-path scheduler treats the PTY as one underlay link dashboard · CLI · REST RSSI / noise / txbuf stale-detection · per-link rssi only, and only with prediction on
One process per radio, and the contract is JSON. Adding a radio family means writing an adapter, not touching the daemon — which is why a 64 kbps serial link and a gigabit fibre are configured the same way.
Anatomy

What an adapter actually does to the byte stream.

The contract above, drawn for the one adapter that sits in the data path. A serial radio hands its port to a separate process; the process hands the daemon an ordinary PPP link and a JSON telemetry feed, and the daemon never learns a byte of the vendor's protocol. The last panel is the honest part: of everything the radio reports, exactly one field is wired to a scheduling decision today.

One radio, one process, two channels — and where each of them lands our processesthe hardwarechanges behaviour
Modem · not ours

A SiK-firmware radio

/dev/rfd → 115200 baud

RFD900x/u/p, HM-TRP, any 433/868/915 MHz SiK board — reached through a stable udev alias, though the adapter's own default is plain /dev/ttyUSB0.

One serial stream carries two things at once: the PPP frames you actually want, and RADIO_STATUS telemetry that the receiving radio's own microcontroller injects in band — dropped into the middle of your data, where a naive reader would hand it straight to pppd as corruption.

in band: RADIO_STATUS · msg id 109 · 9-byte payload

Adapter · its own process, unit and failure domain

atlas-telemetry-sik-adapter

Three synchronous OS threads — receive, transmit, telemetry — and deliberately no async runtime in the data path. Between them sits a strict-filter parser that is not a general protocol decoder: it consumes a frame only when the magic byte, a payload length of exactly 9, a message id of 0 or 109 and a CRC-16/MCRF4XX over the body with that message's own extra byte all agree at once.

Everything that fails any of those tests is handed back to the data side untouched, so a stray 0xFE inside your traffic is never mistaken for telemetry and never silently eaten.

strict filter: magic 0xFE / 0xFD · len 9 · id 0 or 109 · CRC-16/MCRF4XX

Channel 1 · data

A pseudo-terminal at a stable path

/dev/atlas-sik-rfd → /dev/pts/N

Every byte the parser did not claim is written straight through to a PTY, and the adapter keeps a symlink pointed at whichever slave the kernel handed it — so pppd has one path to attach to across restarts and re-enumerations. Atlas never sees this channel. The bytes are opaque to it.

opaque to Atlas — no parsing, no framing, no interpretation

In Atlas

One ordinary underlay link

ppp-sik-rfd → [[link]] name = "sik-rfd"

PPP brings up an interface and that interface is declared as one [[link]] like any other. The per-packet scheduler probes it, scores it and bonds it with the Wi-Fi and the cellular beside it. Nothing in the daemon knows a radio was involved. scheduling ↗

one of N links, chosen or skipped packet by packet

Channel 2 · telemetry

Newline-JSON on a Unix socket

/run/atlas/telemetry.sock

  • rssi_dbm
  • rssi_remote_dbm
  • noise_floor_dbm
  • noise_remote_dbm
  • tx_buffer_free_pct
  • rx_errors
  • fec_corrected

One JSON object per line, keyed by the same link_name as the [[link]] entry, with a fallback to the pre-rename path so an adapter and a daemon from either side of that change still find each other.

Unknown fields are ignored and missing ones tolerated, so a new radio family joins without a schema migration.

source = "atlas-telemetry-sik-adapter"

In Atlas

The snapshot every consumer reads

The daemon keeps the most recent event per link together with the moment it arrived, so every reader can say how old a reading is instead of pretending it is current. From there it joins the once-per-second stats snapshot that the CLI, the dashboard and the REST API all share. telemetry ↗

CLI · dashboard · REST — one snapshot, once a second

Steers the bond

One field, and only when you switch it on

Of the seven, only rssi_dbm reaches a scheduling decision — and only with RSSI prediction enabled, which is off in the default configuration. Even then it does not enter a cost function directly. The monitor keeps an EWMA level and slope of the signal, projects it a couple of seconds ahead, and if that projection lands near the configured floor it inflates the loss rate the scheduler sees — never the measured number the dashboard shows. The inflation is capped, so a fading link is de-weighted but is never declared dead by prediction; only missed probes do that. prediction ↗

Shown, not used

The other six are instrumentation

The noise floor, the remaining TX-buffer space, the uncorrected-error and FEC-corrected counters and both remote-end readings are parsed, stored, drawn in the dashboard's radio columns and served over the API — and they feed no scheduling decision today. Radio SNR is not computed at all, although the two values it needs arrive on the same line. The daemon's own notes call a falling TX-buffer the canonical predictor of impending loss on a rate-limited radio; nothing consumes it yet. That is a wiring gap, not a position.

Why it is shaped this way. One process per radio, and the contract between it and the daemon is a JSON line on a socket. A new radio family means a new adapter beside the daemon rather than a change inside it — which is why Atlas has never had to learn a vendor protocol, and why an adapter that crashes takes nothing else with it.
Serial · in-band

SiK / RFD900 telemetry radios

The only adapter in the data path: radio serial ⇄ PTY bridge extracting MAVLink RADIO_STATUS on the way. Three synchronous OS threads — rx, tx, telemetry — with retry-on-partial writes; this architecture is load-bearing (an async rewrite measurably lost 24–30% of bytes). Strict CRC-validated frame filter, wedge detection that exits so systemd restarts a hung USB radio, and byte-level tap diagnostics for conservation analysis. Telemetry: both ends’ RSSI and noise, TX-buffer %, error and FEC-corrected rates — down to kernel FIFO and PPP queue depths, so you can see where bytes queue when a serial link chokes.

Wi-Fi · poll

802.11 station telemetry

Polls the wireless stack for best-peer RSSI (multi-chain aware), noise floor from the in-use channel’s survey, TX-fail and kernel-drop rates, and peer inactivity as a liveness hint — with workarounds for the driver quirks that split or hide those fields. Feeds the RSSI that drives predictive failover on Wi-Fi links.

Ethernet · poll

Wired truth, including the PHY

Carrier, negotiated speed and duplex, error/drop/CRC/collision rates from sysfs — plus optional PHY-level per-pair SNR margins on gigabit copper. A failing crimp shows up as a rising CRC-error rate before the link ever flaps.

Bluetooth · poll

The interference canary

RSSI, link quality and controller ACL packet rates. The ACL counters are the primary interference signal: under RF pressure TX keeps ticking while RX collapses and the error counters stay at zero — a pattern probes alone can’t attribute.

The other direction

Hardware that listens.

Adapters carry what the radio knows upward. The same boundary could carry intent downward — because Atlas is the only component holding both the cross-vendor topology and what the traffic actually is right now. It can say what “good” means this minute, and let the segment’s own intelligence work out how. Research

Objectives, never knobs. Atlas would never write a channel number or a modulation rate. It would say what it needs for the next few seconds; the radio stays the expert on how to deliver it, and keeps the right to refuse. Every radio already adapts on what it can measure at its own antenna — this is the picture from three hops away, which no radio can see for itself.

The vocabulary

optimize_for

The whole idea in one key: latency, throughput, energy or robustness. Atlas states what matters; the radio picks the modulation, the power and the retry policy that get there.

expect_load

Pre-arm before demand. The scheduler knows a class is about to burst before the bytes arrive — a radio told in advance can be ready instead of reacting.

avoid_transit

Structural intent: stop forwarding for others, Atlas is already routing. Two routing layers fighting over the same topology is a real field problem, and this is the one-line answer to it.

prepare

Intent from geometry. Position and velocity already ride the routing flood: the forward pair enters terrain shadow in about forty seconds — prefer low-latency forwarding.

What it would ask for

Power

Turn it down when the peer is close

Two nodes 200 m apart do not need the EIRP that carries 20 km. Lower power is battery saved and less noise for the rest of the formation to live with. The inverse matters more: Atlas already projects a closing margin, so it can ask for more power before the link degrades rather than after — the same prediction that already moves traffic early.

Robustness

Survive the next twenty seconds; don’t be fast

The clearest case of an objective the radio resolves its own way — a more robust modulation, more retries, a narrower channel, whatever that vendor’s stack believes is right. Atlas knows a deadline-bounded class just took over the segment; the radio knows what its own PHY can do about it. Neither knows both.

Airtime

Duty cycle, spent deliberately

On a licence-exempt band with a duty-cycle budget, Atlas knows exactly how much it has sent this hour and which class spent it. Pacing that budget is a scheduling decision it is already positioned to make — and it can tell the radio how much of the hour is left rather than letting it discover the ceiling.

Slot policy

When the MAC is ours, intent becomes the schedule

The GPS-disciplined TDMA research is the extreme case of this whole idea: Atlas gating its own send loop to a slot grid every node computes identically from the satellites. Once the MAC is ours, intent stops being a hint and becomes an allocation — slots handed out against the actual topology and against the classes each segment is carrying, so an L2 mesh is optimised for what the mission needs this minute rather than for a configuration chosen at deployment. Same vocabulary, direct effect: optimize_for = latency on the segment carrying C2 means that segment gets its slots sooner and more often, not that a knob moved somewhere.

Pointing

Where the peer will be, not where it is

Position and velocity already travel in the routing flood, so a steerable antenna or a gimbal could be told the projected position a few seconds ahead. Its sibling is already on the roadmap as relay autopositioning — intent to the flight layer rather than to the radio.

Coexistence

Two of your own segments are colliding

Atlas can see that two links in the same formation share a band and are hurting each other — a thing neither radio can work out alone, because neither can see the other’s traffic. It would say so. The radio’s own channel selection decides what to do about it; no frequency ever comes from us.

Sleep

Nothing is due for eight seconds

Class deadlines are already known to the scheduler, and store-carry-forward already models traffic that can wait. On a battery node that is permission to idle a radio — the one intent whose value is measured in hours of endurance rather than milliseconds.

The honest edge

Not every bearer has a downward direction

A fibre transceiver offers optical margins to read and essentially nothing to command. Some cellular modems expose almost nothing beyond band locking. The intent surface is worth building where radios are genuinely adaptive; elsewhere the adapter stays exactly what it is today, a reader.

The shape of it

The same boundary, in reverse.

No new protocol and no daemon change — the intent surface is the telemetry socket read the other way, on an adapter that already owns that radio’s management interface.

Atlas → adapter → the radio, and what comes back our processesthe hardware
How an intent surface would work Atlas holds the cross-vendor topology, the traffic classes in flight and the projected geometry. It would express an objective — not a setting — over the same adapter boundary that carries telemetry today. The adapter validates, rate-limits and logs it, then translates it to the vendor's own management interface. The radio decides how to satisfy the objective, or refuses; it keeps its power, channel, modulation and regulatory domain. Existing telemetry already reports whether anything changed. ATLAS what it already knows the whole topologyevery segment, every vendor, one LSDB what is in flightwhich classes each segment carries now where things are goingposition and velocity, projected an objective never a knob ADAPTER the intent surface — the telemetry socket, in reverse authenticate · rate-limitan intent is a request, not a command log every applied intentauditable after the fact translateto the interface the vendor published nl80211 · AT · vendor CLI published interfaces only THE RADIO decides how — or refuses power · channel · modulationits physics, not ours its regulatory domainthe envelope Atlas must never cross free to say norefusal is a valid answer and the telemetry that exists today already reports whether anything changed
Nothing here is built. Today the arrow points one way: adapters read, and nothing writes. This is where the adapter architecture leads, and it is the conversation we want to have with radio vendors.

The rules that would keep it safe

Objectives, never knobs

Never a channel number, never an MCS rate, never a power figure in dBm. The radio owns its physics — the moment we start writing settings we have taken on responsibility for a PHY we do not understand.

Advisory, and slow

Applied with hysteresis and dwell, because there are two adaptive control loops here — Atlas reweighting above, the radio’s own rate adaptation below — and if either moves fast they will oscillate against each other. This is the discipline the shipped prediction engine already follows.

Published interfaces only

No reverse engineering and no writes that could void a certification. If the vendor did not document it, we do not touch it. An integration that jeopardises someone’s type approval is worth nothing to them.

The regulator wins

Power, channel and duty cycle are licensed. Atlas must never move a node outside its envelope, the radio enforces its own regulatory domain, and refusal is always a valid answer — the operator’s licence is not ours to spend.

Fail-open, authenticated, auditable. Intent would carry an authenticated origin through the existing peer-auth machinery, be rate-limited, and leave every applied change in a log. With the feature absent, unimplemented or refused, the underlay behaves exactly as it does today — which is the only way a design like this is safe to ship incrementally.

If you build radios

What it costs you

One small surface on an adapter — the telemetry socket in reverse, on the process that already owns your management interface. Not a change to the daemon, not a protocol to implement, not a dependency to take.

What you keep

Your physics, your certification, your algorithms and your right to refuse any request. Atlas never becomes the thing that decides how your radio behaves — only the thing that tells it what the mission is doing.

What you gain

Your radio adapts today on what it can measure at its own antenna. This gives it the picture from three hops away: the topology, the traffic and where everything is heading. Talk to us about an integration

Field-proven

Telemetry-driven bitrate governor

A small daemon polls the Atlas stats snapshot once a second, scores the alive links against a tier table (1080p/4 Mbps → 720p/2 Mbps → 720p/1 Mbps by loss and RTT), and steps the camera’s encoder accordingly — down instantly, up only after 10 s of sustained health, never more than one tier per decision. Flapping links don’t make the camera oscillate; a stale snapshot means the worst tier, not a guess. Backends: a gimbal-camera SDK, an arbitrary shell template for any camera, and dry-run.

Pattern

Closed loop, open boundary

The governor is deliberately outside the daemon — another adapter-shaped companion reading the same snapshot every consumer reads. Any sensor whose output rate can be commanded can close the same loop.

Underlays

What counts as a link?

Worth a reminder, because the answer is wider than most bonding products allow. If a bearer can carry a UDP datagram — natively, over PPP, or through a tun that a radio stack hands you — Atlas will bond it with everything else on the node and score it on its own terms.

BearerHow it joinsWorth knowing
Ethernet / fibreNative IP linkPHY-level SNR telemetry on gigabit copper — a failing crimp shows as a rising CRC rate before the link ever flaps
Microwave point-to-pointNative IP linkBehaves like fibre until the weather arrives — the case gradual reweighting exists for
Wi-Fi (AP/STA)Native IP linkRSSI-driven prediction; a re-enumerated USB adapter is rebound by the watchdog
802.11s meshThe whole segment as one measured pathOne multi-hop layer per radio set — the coexistence rule
802.11ah HaLowNative IP linkSub-GHz reach and penetration — the survival link that still carries real data
wfb-ng raw Wi-FiPresents a tun; bonded as an ordinary linkDon’t double up FEC — the radio layer already has its own
LTE / 5G cellularNative IP link (per-network sockets on Android)Endpoint roaming keeps sessions alive through carrier address changes
LEO constellation terminalNative IP linkHandover gaps look like short degradations; bond a second operator rather than trusting one
GEO VSAT terminalNative IP linkGive it its own probe interval (1000 ms) or the physics reads as loss
SiK / RFD900 serialPPP over the adapter’s PTY64 kbps-class links are first-class citizens: declared capacity keeps bulk off them automatically
HF modem (NVIS)PPP over serial, like any telemetry radioOnly the Control and Position classes belong here; everything else waits in the store
Bluetooth PPPPPP over RFCOMMThin but genuinely independent — and a fine interference canary
Hosted / internet pathsRelay and gateway linksReachability without inbound rules — traversal
Custom SDR waveformAnything presenting a tun or a UDP socketThe bond neither knows nor cares what the physics are — declare the capacity honestly

Six dissimilar types have run bonded on a single field node. Others on this list join in exactly the same way — the daemon special-cases none of them — though we have not personally fielded every row.

Reach

Which bearers are in the bond depends on where you are.

Every radio link can declare a range envelope, and a link outside its envelope is not offered traffic. Fly outward and the bond loses members one envelope at a time — but it never fails over to a survivor, because it was never using one link at a time. The connection is continuous across all three positions below; only its composition changes.

Outbound transit — the bond recomposing as reach envelopes are crossedSimulation
Position
bearers in the bond
declared across the bond
from the ground station
next envelope to be crossed
What you’re watching: a simulation of the rule per-link range envelopes already implement — [[link]] max_range_m with a warn radius at 60% of it. Inside the warn radius a bearer scores at full weight; past it its cost rises with distance, which is why its strand thins before the arc; beyond max range it is out of the bond. The bearer set, the distances and the split are illustrative; the behaviour is not. “Contested airspace” here means only that the short-reach civil bearers are gone — the picture makes no claim about the RF environment out there.
Integration

Bring your own bearer.

Three steps, and none of them are in the daemon’s source tree — this is the integration surface, deliberately narrow.

Present IP

Natively, as PPP over the adapter’s PTY for a serial radio, or as a tun handed to you by a raw-Wi-Fi or SDR stack. Whatever carries a datagram is enough — the bond asks nothing else of the physics.

Declare it

Capacity in human units, a probe interval that suits the path, a range envelope if it is a radio, and its own listen port if it shares a modem with another link. Four lines of TOML, validated at load.

Feed telemetry

Optional, and the step that changes behaviour: a small companion process pushing RSSI, noise or buffer depth into the telemetry socket lets the bond steer on what the radio knows, not just on what the probes measure.

Field-proven

Linux — x86-64 and ARM64

One binary from servers to ~15 g single-board computers; systemd units, SELinux/AppArmor profiles and an Ansible role ship as reference packaging. This is the supported platform, and the whole site describes it.

Early implementation

Android — the same engine, evaluation builds

The engineering is the interesting part: the app embeds the unmodified Rust core through a JNI bridge, with ownership split cleanly — the app owns the TUN descriptor and per-network sockets (each bound to its physical network and VPN-protected before handoff), the engine owns the protocol. Wi-Fi + cellular bond with per-link telemetry; losing a radio never restarts the engine (the link dies by probe timeout and traffic routes around it), and a returning radio hands the engine a fresh socket in place. QR pairing, hosted-gateway ranking by measured RTT, per-app routing, and a full-tunnel bonded exit mode. Honest platform limits: one VPN per Android profile, and consumer Android can’t hold two cellular data sessions.

Tours and evaluation builds → nexusatlas.app

Library

Embedding

The engine is a library first — the daemon is a thin CLI over it, and the same external-descriptor mechanism the Android bridge uses (adopt a TUN and per-link sockets from an external owner) is available to other embedders, behind a compile-time feature that is off by default.

Maps

The map ships with the node

Topology views need a basemap, and a node in a hangar has no internet to fetch tiles from. GET /api/basemaps lists what this node holds and serves each file verbatim, so the map renders from the node itself. Basemaps are built offline with the tooling in tools/ — a world tier from public-domain Natural Earth data, and area-of-operations tiers from OpenStreetMap.

Request an evaluation

Your radio has more to say than “up”.

If it presents IP — or a serial port — it can join the bond. If it reports RSSI, it can steer the bond. Tell us what hardware you run.