Starlink Video Link
for UAVs

Lightweight Raspberry Pi module that turns Starlink into a reliable HD video and telemetry downlink for drones — stream 1080p and MAVLink data over satellite from anywhere.

Buy the kit → Download Windows viewer (unsigned) Documentation
1080p
HD Video
SRT
Over Starlink
MAVLink
Telemetry
<200ms
Latency

Features

Built for the battlefield, tested in the field

Starlink-Optimised Streaming

SRT protocol with local encoder backoff and forward error correction tuned for satellite uplink. Streams H.264 reliably over Starlink's variable bandwidth.

MAVLink Over Satellite

Forward telemetry from ArduPilot/PX4 over Starlink to any GCS, cloud, or analytics endpoint. Serial and TCP input with CRC validation and multi-destination routing.

Live Map & Tracking

Track your UAV in real-time on an interactive map with GPS trail, flight mode, altitude, speed, and satellite count — accessible from anywhere via Starlink.

Dual Camera Support

RPi Camera Module and thermal imagers (V4L2). Crash-safe MKV recording on-board with simultaneous live stream over satellite — never lose footage.

VPN Mesh + WiFi Uplink

Built-in Tailscale + ZeroTier create a secure mesh between the UAV, Starlink, and your command post. WiFi management UI scans + connects from the browser — no SSH needed. Access the dashboard from any device, anywhere on the planet.

Lightweight & Field-Ready

Runs on a 45g Raspberry Pi. Plug a USB stick labelled AIRREC and recordings auto-flow to it — SD card stays clean. Direct laptop-to-Pi cable works without a router. Configure everything through the web dashboard over Starlink or local Ethernet.

Self-Diagnosing Hardware

Live SD/eMMC wear visibility (JEDEC wear buckets), PSU under-voltage detection, per-stage pipeline FPS chip, and audit-logged guidance state transitions. Fails forward: alert pill warns months before the SD card dies in the field.

On-Device AI Tracking STRIKE

Optional Pi 5 + Hailo-8 variant runs YOLOv8 on-device at 30 fps. Operators tap a target in the live video; the Air ground station locks on and autonomously guides an ArduPlane airframe onto it. Compile-time + license gate keep public builds log-only. Read more →

How It Works

Direct Ethernet first: the reliable setup path before WiFi, ZeroTier, or license activation

1

Flash

Flash air-os-latest.img.xz to an SD card with Raspberry Pi Imager. One image works on Pi 4, Pi 5, CM4, and CM5.

Watch the SD card + CM4/CM5 eMMC flashing tutorial. Watch the physical hookup walkthrough. Watch Windows adapter, ZeroTier, and license setup. Watch the AirOS first setup walkthrough.

2

Boot

For first setup, plug Ethernet directly between laptop and Pi. On Windows, set the Ethernet adapter to manual IPv4 192.168.144.10, subnet mask 255.255.255.0, gateway/DNS blank. Power on, wait 60-120 seconds, then open http://192.168.144.1/. air.local is optional and only works on router/LAN setups with mDNS/Bonjour.

3

Activate

Open the web UI (admin/admin, force-rotate on first login). Configure WiFi, ZeroTier, System, Backup, and other setup pages before activation if needed. Paste your license key only after the Pi has internet; the license gates video transmission and telemetry forwarding, not basic setup.

4

Fly & Stream

Live H.264 / H.265 video over WebRTC preview, RTP/UDP, or SRT. MAVLink telemetry forwarded to any GCS. Real-time map. Strike variant adds AI tracking + autonomous terminal guidance.

Step 1

Flash the image

Write Air OS to a fresh SD card or CM4/CM5 eMMC.

Step 2

Connect the hardware

Insert storage, connect Ethernet/LAN, and bring up Raspberry Pi, CM4, or CM5 hardware.

Step 3

Join ZeroTier and activate

Set the Windows Ethernet adapter, connect to your custom ZeroTier network, and activate the license once the Pi is online.

Step 4

Open AirOS

Reach the web UI through direct Ethernet, log in, get the Pi online, and activate after internet is available.

Pricing

Software is free to try. A license is only required to run production missions; hardware kits bundle it at no extra cost.

Software

Air OS License

Standard variant only. Perpetual license keyed to one Raspberry Pi hardware ID. Use with your own Pi + camera + Starlink hardware.

11,193 UAH
$250 USD · license only, bring your own hardware
  • Camera pipeline (CSI / USB / RTSP / thermal)
  • MAVLink telemetry over Starlink / SRT / UDP / TCP
  • ZeroTier / Tailscale VPN, OTA updates
  • Recording, flight log, REST + WebSocket API
  • AI tracking + autonomous guidance not included
Request license →
Add-on

Strike Variant Upgrade

AI object tracking + autonomous terminal guidance. Hailo-8 HAT on Pi 5 unlocks the full AI tracker; the same image runs the operator-tap targeting path on Pi 4 / Pi 5 without HAT (no AI, manual selection only). End-user verification required before enable.

67,157 UAH
$1,500 USD · upgrade on top of any OS license
  • YOLOv8 on-device at 30 fps (Hailo-8, 26 TOPS)
  • Tap-to-select targeting from any connected viewer
  • ArduPlane GUIDED-mode autonomous approach
  • OSD overlay baked into every outbound video stream
  • Dual gate: compile-time flag + signed license claim
  • Audit-logged guidance state transitions for post-mission review
  • End-user verification + export-control self-cert
Request strike upgrade →

UAH prices shown at the official NBU rate of 44.77 UAH / USD — USD is canonical. The Air OS software itself is free to download and try — flash it on any Raspberry Pi. A license is only required to run video and telemetry transmission on production hardware.

Downloads

Get Air for Raspberry Pi, Intel N100 mini PCs, or viewer apps for laptops and Android controllers

Air OS Standard — Pi 4 / Pi 5 / CM4 / CM5

Current verified unified Pi 4 / Pi 5 / CM4 / CM5 image. Flash it, connect Ethernet, then open 192.168.144.1.

Download Standard (.img.xz)

~720 MB. First SSH login: air:air; change the password after setup.

Details and advanced options

Standard is the recommended Pi image for camera, telemetry, VPN, joystick, maps, and normal OTA updates. Use Strike only when you specifically need the Hailo/AI tracking build.

⚠ First-boot SSH credential is air:air. Change the password with passwd after first login. Do not expose :22 to the public internet before changing it.

Verify your download (SHA256 + Ed25519)

Authenticate the checksum with the independently pinned AirOS release key before trusting the image. Set air_image to the Strike filename when downloading Strike. The AIR_OS_INSTALLER_SIGNATURE_V2 statement also binds the provenance and adjacent verifier bytes. The fail-closed inline bootstrap authenticates them without executing code from the download mirror.

(
set -euo pipefail
umask 077
AIR_INSTALLER_PUBKEY=f79ecbda6904e34556616c7a537dea8740609b19f07a556b9eb783d7751838fb
air_image=air-os-latest.img.xz # air-os-latest-strike.img.xz for Strike
air_download_dir="$(pwd)/air-os-verified"; mkdir -- "$air_download_dir"; cd -- "$air_download_dir"
air_verify_tmp="$(mktemp -d ./verify.XXXXXX)"; trap 'rm -rf -- "$air_verify_tmp"' EXIT
air_fetch() {
  local f="$1" max="$2" size partial="$air_verify_tmp/download.part"
  rm -f -- "$partial"
  if ! curl --proto '=https' --tlsv1.2 --fail --location --show-error --max-filesize "$max" "https://air.airdroper.org/downloads/$f" | head -c "$((max + 1))" >"$partial"; then rm -f -- "$partial"; return 1; fi
  size="$(stat -c '%s' "$partial")"
  if ! [[ "$size" =~ ^[0-9]+$ ]] || test "$size" -le 0 || test "$size" -gt "$max"; then rm -f -- "$partial"; return 1; fi
  mv -f -- "$partial" "$f"
}
air_fetch "$air_image.sha256" 65536
air_fetch "$air_image.sha256.sig" 256
air_fetch "$air_image.provenance" 1048576
air_fetch "$air_image.verify.sh" 1048576
air_fetch air-os-installer-ed25519.pub 128
key="${AIR_INSTALLER_PUBKEY,,}"; test "$(tr -d '\r\n' < air-os-installer-ed25519.pub)" = "$key"
test "$(wc -l <"$air_image.sha256" | tr -d '[:space:]')" = 1
checksum_line="$(cat "$air_image.sha256")"; checksum_digest="${checksum_line%%  *}"; checksum_name="${checksum_line#*  }"
[[ "$checksum_digest" =~ ^[0-9a-f]{64}$ ]]; test "$checksum_line" = "$checksum_digest  $air_image"; test "$checksum_name" = "$air_image"
checksum_size="$(stat -c '%s' "$air_image.sha256")"; checksum_sha="$(sha256sum "$air_image.sha256" | awk '{print $1}')"
provenance_size="$(stat -c '%s' "$air_image.provenance")"; provenance_sha="$(sha256sum "$air_image.provenance" | awk '{print $1}')"
verifier_size="$(stat -c '%s' "$air_image.verify.sh")"; verifier_sha="$(sha256sum "$air_image.verify.sh" | awk '{print $1}')"
printf 'AIR_OS_INSTALLER_SIGNATURE_V2\nname=%s.sha256\nsize=%s\nsha256=%s\nprovenance_name=%s.provenance\nprovenance_size=%s\nprovenance_sha256=%s\nverifier_name=%s.verify.sh\nverifier_size=%s\nverifier_sha256=%s\n' "$air_image" "$checksum_size" "$checksum_sha" "$air_image" "$provenance_size" "$provenance_sha" "$air_image" "$verifier_size" "$verifier_sha" >"$air_verify_tmp/statement"
signature="$(tr -d '\r\n' <"$air_image.sha256.sig")"; [[ "$signature" =~ ^[A-Za-z0-9+/]{86}==$ ]]
printf '%s' "$signature" | openssl base64 -d -A -out "$air_verify_tmp/signature.bin"; test "$(wc -c <"$air_verify_tmp/signature.bin")" = 64
printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00' >"$air_verify_tmp/public.der"; for ((offset=0; offset<64; offset+=2)); do printf "\\x${key:offset:2}"; done >>"$air_verify_tmp/public.der"
openssl pkeyutl -verify -pubin -keyform DER -inkey "$air_verify_tmp/public.der" -rawin -in "$air_verify_tmp/statement" -sigfile "$air_verify_tmp/signature.bin"
air_fetch "$air_image" 8589934592
test "$(sha256sum "$air_image" | awk '{print $1}')" = "$checksum_digest"
printf 'Verified image retained at %s/%s\n' "$air_download_dir" "$air_image"
)
Current SHA256: loading…

Air OS Standard — Intel N100 / x86_64 mini PC

For Ubuntu/Debian mini PCs with SSD/NVMe. This is not a flashable image: unpack the package and run the installer on the existing OS.

Download N100 package (.tar.xz)

After install, open http://<mini-pc-ip>/. First web login is admin / admin.

Details and advanced options

Use this for N100/N95/N200-class boxes that already boot Ubuntu/Debian. It uses the separate x86_64 OTA lane and Intel VAAPI video acceleration when available.

Verify your download (SHA256 + Ed25519)
(
set -euo pipefail
umask 077
export AIR_INSTALLER_PUBKEY=f79ecbda6904e34556616c7a537dea8740609b19f07a556b9eb783d7751838fb
air_verify_tmp="$(mktemp -d ./.air-n100-verify.XXXXXX)"; trap 'rm -rf -- "$air_verify_tmp"' EXIT
air_fetch() {
  local f="$1" max="$2" size partial="$air_verify_tmp/download.part"
  rm -f -- "$partial"
  if ! curl --proto '=https' --tlsv1.2 --fail --location --show-error --max-filesize "$max" "https://air.airdroper.org/downloads/$f" | head -c "$((max + 1))" >"$partial"; then rm -f -- "$partial"; return 1; fi
  size="$(stat -c '%s' "$partial")"
  if ! [[ "$size" =~ ^[0-9]+$ ]] || test "$size" -le 0 || test "$size" -gt "$max"; then rm -f -- "$partial"; return 1; fi
  mv -f -- "$partial" "$f"
}
air_fetch air-os-n100-latest.tar.xz.sha256 65536
air_fetch air-os-n100-latest.tar.xz.sig 256
air_fetch air-os-installer-ed25519.pub 128
key="${AIR_INSTALLER_PUBKEY,,}"; test "$(tr -d '\r\n' < air-os-installer-ed25519.pub)" = "$key"
test "$(wc -l < air-os-n100-latest.tar.xz.sha256 | tr -d '[:space:]')" = 1
checksum_line="$(cat air-os-n100-latest.tar.xz.sha256)"; checksum_digest="${checksum_line%%  *}"
test "$checksum_line" = "$checksum_digest  air-os-n100-latest.tar.xz"; [[ "$checksum_digest" =~ ^[0-9a-f]{64}$ ]]
tr -d '\r\n' < air-os-n100-latest.tar.xz.sig | openssl base64 -d -A -out package.sig.bin
test "$(wc -c < package.sig.bin)" -eq 64
printf '\x30\x2a\x30\x05\x06\x03\x2b\x65\x70\x03\x21\x00' > installer-public.der
for ((i=0; i<64; i+=2)); do printf "\\x${key:i:2}"; done >> installer-public.der
air_fetch air-os-n100-latest.tar.xz 8589934592
name=air-os-n100-latest.tar.xz; size="$(stat -c '%s' "$name")"; sha="$(sha256sum "$name" | awk '{print $1}')"
printf 'AIR_OS_INSTALLER_SIGNATURE_V1\nname=%s\nsize=%s\nsha256=%s\n' "$name" "$size" "$sha" > package.signature-input
openssl pkeyutl -verify -pubin -keyform DER -inkey installer-public.der -rawin -in package.signature-input -sigfile package.sig.bin
test "$sha" = "$checksum_digest"
)

Air Viewer (Windows)

Free standalone viewer with low-latency decode, recording, telemetry overlay, camera switching, and English/Ukrainian UI. Auto-detects H.264 / H.265 from AirOS. The current public build is unsigned and automatic update checks are disabled.

Download unsigned .exe

Unsigned preview: Windows will show Unknown Publisher because this file has no Authenticode certificate. Verify its SHA-256 before running it. A checksum detects changed bytes but does not authenticate Airdroper as the publisher.

Details and advanced options

This operator-approved unsigned lane is separate from the future signed production lane. It publishes immutable versioned bytes, a checksum, build/security evidence, and a plain release record, but it does not publish signed updater metadata and does not replace the signed-production aliases.

View the unsigned release record

Signed production status: No approved Air Viewer Authenticode signer fingerprint is published yet. The signed-production aliases remain unavailable; the unsigned preview above is a separate, explicitly labelled lane.

After release engineering independently approves a concrete signer fingerprint, the signed executable will require a timestamped Authenticode signature, SHA-256 checksum, and signed update metadata. Do not treat the unsigned preview checksum as publisher authentication.

Post-release signed download and pinned-publisher check
$approvedSignerSha256 = (Read-Host 'Paste the independently approved 64-hex signer SHA-256').Trim().ToLowerInvariant()
if ($approvedSignerSha256 -cnotmatch '^[0-9a-f]{64}$') { throw 'No approved signer fingerprint supplied' }
Invoke-WebRequest https://air.airdroper.org/downloads/air-viewer-latest.exe -OutFile air-viewer-latest.exe
Invoke-WebRequest https://air.airdroper.org/downloads/air-viewer-latest.exe.sha256 -OutFile air-viewer-latest.exe.sha256
$expected = (Get-Content .\air-viewer-latest.exe.sha256).Split()[0]
$actual = (Get-FileHash .\air-viewer-latest.exe -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $expected) { throw 'SHA-256 mismatch' }
$signature = Get-AuthenticodeSignature .\air-viewer-latest.exe
if ($signature.Status -ne 'Valid') { throw "Invalid publisher: $($signature.Status)" }
if ($null -eq $signature.SignerCertificate) { throw 'Missing signer certificate' }
if ($null -eq $signature.TimeStamperCertificate) { throw 'Missing trusted timestamp' }
$signerSha256 = $signature.SignerCertificate.GetCertHashString([System.Security.Cryptography.HashAlgorithmName]::SHA256).ToLowerInvariant()
if ($signerSha256 -cne $approvedSignerSha256) { throw 'Publisher fingerprint mismatch' }
Verify unsigned download bytes (SHA-256)
$base = 'https://air.airdroper.org/downloads'
$recordText = (Invoke-WebRequest "$base/air-viewer-unsigned-current/release.txt").Content
$expectedKeys = @(
  'schema', 'version', 'source_revision', 'release_mode',
  'authenticode_status', 'authenticode_required', 'sha256', 'size_bytes',
  'approval_checksum_sha256', 'latest_checksum_sha256',
  'build_manifest_sha256', 'dependency_manifest_sha256', 'sbom_sha256',
  'security_evidence_sha256', 'update_trust_sha256', 'immutable_url',
  'build_generated_utc', 'warning'
)
$lines = $recordText.TrimEnd("`r", "`n").Split("`n")
if ($lines.Count -ne $expectedKeys.Count) { throw 'Invalid release record field count' }
$record = @{}
for ($i = 0; $i -lt $lines.Count; $i++) {
  if ($lines[$i] -cnotmatch '^([a-z][a-z0-9_]*)=(.+)$') { throw 'Invalid release record line' }
  $key = $Matches[1]; $value = $Matches[2]
  if ($key -cne $expectedKeys[$i] -or $record.ContainsKey($key)) { throw 'Invalid release record field order' }
  $record[$key] = $value
}
if ($record.schema -cne 'org.airdroper.air-viewer.public-unsigned.v3') { throw 'Invalid release schema' }
if ($record.immutable_url -cnotmatch '^https://air\.airdroper\.org/downloads/air-viewer-unsigned-releases/v[0-9]+\.[0-9]+\.[0-9]+/air-viewer\.exe$') { throw 'Unsafe immutable URL' }
$immutableBase = $record.immutable_url.Substring(0, $record.immutable_url.LastIndexOf('/'))
Invoke-WebRequest $record.immutable_url -OutFile air-viewer.exe
Invoke-WebRequest "$immutableBase/air-viewer.exe.sha256" -OutFile air-viewer.exe.sha256
$line = (Get-Content -Raw .\air-viewer.exe.sha256).TrimEnd("`r", "`n")
if ($line -cnotmatch '^([0-9a-f]{64})  air-viewer\.exe$') { throw 'Invalid checksum file' }
$sidecarHash = (Get-FileHash .\air-viewer.exe.sha256 -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sidecarHash -cne $record.approval_checksum_sha256) { throw 'Checksum evidence mismatch' }
$actual = (Get-FileHash .\air-viewer.exe -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -cne $Matches[1] -or $actual -cne $record.sha256) { throw 'SHA-256 mismatch' }
if ((Get-Item .\air-viewer.exe).Length -ne [int64]$record.size_bytes) { throw 'Size mismatch' }
$signature = Get-AuthenticodeSignature .\air-viewer.exe
if ($signature.Status -ne 'NotSigned') { throw "Unexpected signature state: $($signature.Status)" }
Write-Warning 'SHA-256 passed; this preview is intentionally unsigned and Windows cannot authenticate its publisher.'

Air Viewer (Android)

Native APK for Android phones and controller screens such as SIYI MK32. Auto chooses RTSP passthrough when available, falls back to the AirOS Web Preview for USB/WebRTC cameras, and supports low-latency OSD overlays. ~5 MB.

Download APK

Install from Android Files after enabling installs from unknown apps for your browser or file manager.

Details and advanced options

Use Auto mode for AirOS. Setup links such as airviewer://connect?host=192.168.144.1&mode=auto&start=1 can prefill and start the Android viewer. Release upgrades must retain certificate SHA-256 929906c64e87507d023f51365b7e0ed953753ebd484e9951aeb7827c3d44cee2.

Verify your download (SHA256)
curl -L -o air-viewer-android-latest.apk https://github.com/AirdroperUA/air-releases/releases/latest/download/air-viewer-android-latest.apk
curl -sS -L https://github.com/AirdroperUA/air-releases/releases/latest/download/air-viewer-android-latest.apk.sha256 | sha256sum -c -
apksigner verify --verbose --print-certs air-viewer-android-latest.apk
# Signer #1 certificate SHA-256 digest must be:
# 929906c64e87507d023f51365b7e0ed953753ebd484e9951aeb7827c3d44cee2