Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt

Use this file to discover all available pages before exploring further.

Agent Services

The ADOS Drone Agent uses a multi-process architecture where each service runs as an independent systemd unit. A supervisor service manages the lifecycle. Services communicate through Unix domain sockets and share no memory. The agent is a hybrid system. The core long-running services (the supervisor, MAVLink router, cloud relay, and video pipeline) are native Rust binaries. Python handles the FastAPI web layer, AI and vision, the scripting engine, HAL detection, device drivers, and shared libraries. Several ground-station services (the radio data plane, uplink matrix, physical UI, and display) have native Rust implementations that are opt-in today and fall back to the packaged Python service otherwise. The supervisor never spawns these processes itself: it issues systemctl against a fixed catalog, so systemd remains the process manager and owns the cgroup, restart, and journald wiring.

Why multi-process

A single in-process design is simpler, but it has real drawbacks for a drone:
  • A crashed video encoder takes down the MAVLink proxy. The flight controller loses its companion link.
  • No per-service resource limits. A memory leak in one service starves the others.
  • No per-service restart. Fixing a video pipeline issue requires restarting everything.
The multi-process design isolates failures. A crashed ados-video gets restarted by systemd in 3 seconds. The MAVLink proxy never notices.

Service tree

Distributed-receive roles add three more ground-station units when enabled: ados-batman (mesh carrier), ados-wfb-relay (relay role), and ados-wfb-receiver (receiver role). The supervisor starts child services based on the active profile (air or ground-station) and the hardware detected. Services that depend on hardware not present are masked, not started.

Systemd unit structure

Each service has a unit file in /etc/systemd/system/. Native Rust services run a binary from /opt/ados/bin/; the FastAPI service and the Python-backed services run through the virtual environment in /opt/ados/venv/.
[Unit]
Description=ADOS MAVLink Router
After=ados-supervisor.service
PartOf=ados-supervisor.service

[Service]
Type=simple
User=ados
# Native Rust core service. Python-backed units (for example ados-api)
# use /opt/ados/venv/bin/python -m ados.services.<name> instead.
ExecStart=/opt/ados/bin/ados-mavlink
Restart=on-failure
RestartSec=3
MemoryMax=128M
CPUQuota=50%

[Install]
WantedBy=ados-supervisor.service
Key properties:
  • PartOf: service stops when the supervisor stops
  • Restart=on-failure: automatic restart on crash
  • MemoryMax / CPUQuota: cgroup limits prevent any single service from starving the system

IPC: Unix domain sockets

Services communicate through two Unix domain sockets in /run/ados/: Binary protocol. Each frame is a 4-byte little-endian length prefix followed by raw MAVLink bytes.
| LENGTH (4 bytes, LE) | MAVLink v2 frame (LEN bytes) |
The MAVLink service writes decoded FC messages to this socket. Other services (cloud, scripting, API) read from it. This is a publish-subscribe pattern implemented over a Unix socket. Multiple readers get all messages.

State socket (/run/ados/state.sock)

JSON protocol at 10 Hz. Each frame is a newline-delimited JSON object:
{"ts": 1713270934.5, "mode": "LOITER", "armed": false, "alt": 45.2, "bat": 82, "gps_sats": 12}
The health service reads this to compute system metrics. The cloud service reads it for telemetry upload. The OLED service reads it for display rendering.

Circuit breaker

The supervisor implements a circuit breaker pattern for each service. If a service crashes 5 times within 60 seconds, the breaker opens and the service is not restarted until a manual reset or a supervisor restart.
Normal → 5 failures in 60s → Breaker open → Manual reset or supervisor restart → Normal
When a breaker opens, the supervisor:
  1. Logs a CRITICAL event
  2. Sends a notification to Mission Control
  3. Continues running all other services
A single bad service does not bring down the whole agent.

Profile detection

On first boot (or when agent.profile: auto is set), the profile_detect module runs a score-based hardware fingerprint:
SignalGround scoreAir score
I2C OLED at 0x3C or 0x3D+30
4 GPIO buttons with pull-ups+20
RTL8812EU USB device+1+1
MAVLink serial device (ttyACM or UART)0+3
GPS serial device0+2
FC heartbeat received within 10 seconds0+3
Known FC carrier board profile0+2
Decision rules:
  • Ground score >= 4 AND air score <= 2: ground-station profile
  • Air score >= 4 AND ground score <= 2: air profile
  • Ambiguous: unconfigured, show pick-profile UI
The result is written to /etc/ados/profile.conf with the full fingerprint snapshot. Explicit agent.profile: in config.yaml always overrides detection.

Service lifecycle per profile

Always started:
  • ados-supervisor, ados-mavlink, ados-api, ados-cloud, ados-health
Hardware-dependent:
  • ados-video (if camera detected)
  • ados-wfb in TX mode (if RTL8812EU detected)
  • ados-peripherals (if USB sensors detected)
On-demand:
  • ados-scripting (if enabled in config)
  • ados-ota, ados-discovery
Masked (never started):
  • All ground-station services (hostapd, oled, buttons, kiosk, etc.)

FastAPI REST server

The ados-api service runs a FastAPI server on port 8080. It provides:
  • /api/status and /api/status/full for agent state
  • /api/video/* for video pipeline status and MediaMTX integration
  • /api/v1/ground-station/* for ground-station-specific endpoints (WiFi, pairing, OLED, buttons, uplinks)
  • /api/command for drone commands (arm, disarm, mode change)
  • /api/config for reading and writing agent configuration
Authentication uses the X-ADOS-Key header with a key stored in /etc/ados/config.yaml. The key is generated at install time.

Runtime boundary

The REST server exposes a stable HTTP surface, but it does not own flight controller, video, WFB-ng, script, or parameter state directly. Route modules read those domains through the API runtime facade. In main agent mode, the facade wraps the active process runtime. In standalone API service mode, it reads live telemetry from state IPC and uses public runtime handles for optional subsystems. This boundary is internal to the agent. It does not change request paths, response bodies, or OpenAPI schemas.

HAL board profiles

Each supported SBC has a YAML profile in src/ados/hal/boards/. The profile defines:
# Example: pi4b.yaml
vendor: "Raspberry Pi"
name: "Pi 4B"
soc: "BCM2711"
ram_mb: 4096
tier: 3
uart:
  - path: /dev/ttyAMA0
    baud: 921600
    purpose: mavlink
i2c:
  - bus: 1
    purpose: oled
gpio:
  buttons: [5, 6, 13, 19]
usb:
  ports: 4
video:
  encode: "libx264"  # no hardware encoder on Pi 4B
hdmi:
  available: true
usb_gadget:
  available: true
  otg_port: "usb0"
The profile drives service startup, GPIO mapping, video encoder selection, and feature gating. Unknown boards fall back to safe defaults (I2C bus 1, libx264 encoding, generic GPIO).

Resource budget

Total memory usage for the ground-station profile on a Pi 4B with 4 GB RAM:
ServiceTypical RAM
ados-supervisor15 MB
ados-api (FastAPI)40 MB
ados-wfb-rx20 MB
ados-mediamtx-gs30 MB
ados-hostapd5 MB
ados-oled10 MB
ados-buttons5 MB
ados-health10 MB
ados-kiosk (Chromium)280-520 MB
Total (no kiosk)~135 MB
Total (with kiosk 720p)~415 MB
On a 4 GB Pi 4B, this leaves over 3.5 GB free without kiosk, or about 3.5 GB free with kiosk at 720p.

What is next