New to the platform shape? Start with the Platform Introduction and Platform Architecture Overview. For an end-to-end walk of a request, see Cross-System Data Flows.
The mental model
The Dashboard (browser) talks to the Gateway (cloud) over HTTP + Socket.IO. The Gateway is the control-plane hub: it authenticates users, brokers telemetry, and dispatches commands over rosbridge to a vehicle — either a physical drone reached through a WireGuard VPN, or a SITL Docker container the Gateway spawns. Video and manual gamepad control travel their own out-of-band paths. The key insight the whole platform is built on: SITL and physical drones speak the exact same rosbridge/topic contract, so the Gateway treats them almost identically. The only real differences are how it addresses them (a VPN IP vs a Docker host) and that it manages the SITL container’s lifecycle itself.Vehicles & simulation
drone
The generic unit the platform controls. Every drone has a
type — physical or sitl — defined in src/utils/drone_types.py (DroneTypes enum), and a vehicle_type (copter or rover). From the Gateway’s API and service layer, both types look the same. See Drone Fleet Management.physical drone
A real aircraft running SkyCore Drone OS on an NVIDIA Jetson. It is reachable from the Gateway only after it joins the drone WireGuard plane (
10.71.0.0/16); the Gateway connects rosbridge to its 10.71.x address (or routes through the jumphost). See Physical drone activation.SITL (Software-In-The-Loop)
A hardware-free simulated drone: a Docker container running ArduPilot SITL + MAVROS + rosbridge + a GStreamer→WHIP video node, standing in for a real aircraft. The Gateway’s
SITLDroneService spawns containers named SKYHUB_SITL_<n> (only when ENABLE_SITL=true), maps rosbridge to host port 9090+n, and connects to DOCKER_HOST_IP. See SITL Drone Lifecycle and the SITL Simulator.SkyCore Drone OS
The on-drone software layer: a ROS2 (Humble) + Docker microservice stack on the Jetson — a
core service (MAVROS, rosbridge, video, precision landing), a gamepad/ws_proxy service, an Isaac Visual SLAM service, and mavp2p as the single MAVLink router. See SkyCore Drone OS Overview.Control & telemetry transport
MAVLink
The binary messaging protocol spoken by the ArduPilot flight controller (FCU) —
COMMAND_LONG, SET_MODE, SET_POSITION_TARGET_LOCAL_NED, VISION_POSITION_ESTIMATE, etc. Everything a drone ultimately does is a MAVLink message to the FCU. See MAVLink Routing.MAVROS
The ROS2 node bridging MAVLink to ROS2 topics and services (
/mavros/state, /mavros/cmd/arming, /mavros/global_position/global, …). Gateway commands are expressed as MAVROS service calls / topic publishes. Topic and service names live in src/utils/mavros_topics.py.mavp2p
The single lightweight MAVLink router on the drone. It bridges the FCU serial link (
/dev/ttyACM0 @ 115200) to UDP fan-out: 14550→MAVROS, 14777→gamepad, 14560→RTK. It is the one hub every on-drone service talks MAVLink through. See MAVLink Routing.rosbridge
A WebSocket server (port 9090) on the drone/SITL that re-exposes ROS2 topics and services as JSON (
advertise/subscribe/call_service). This is the Gateway’s command-and-telemetry channel to the vehicle. The Gateway keeps a pooled, auto-reconnecting Connection per drone. See Rosbridge Connection & Reconnect.Socket.IO telemetry
The Gateway→UI real-time channel. The Dashboard opens a Socket.IO connection with
?token=<jwt>, emits subscribe_telemetry{drone_id, stream_type}, and receives telemetry_data events. Rooms are drone-scoped: drone_{drone_id}_{stream_type}. See Socket.IO Telemetry Streaming and the client side.Telemetry stream types
Requested viasubscribe_telemetry{stream_type}; each maps to MAVROS topics fanned out over rosbridge.
stream_type | Source topics | Notes |
|---|---|---|
gps | /mavros/global_position/global | Position only |
logs | /rosout | ROS log messages |
relalt | /mavros/global_position/rel_alt | Relative altitude |
system | /diagnostics | System diagnostics |
dashboard | GPS + GPS_RAW + rel_alt + /rosout + VFR_HUD + IMU + home + /diagnostics | Combined stream with server-computed yaw |
Network & VPN
Three separate WireGuard planes run on the single VPN host (see the User VPN service and Network & VPN Topology).| Plane | CIDR | UDP port | Interface | Who lives here |
|---|---|---|---|---|
| Core | 10.69.0.0/16 | 51822 | wg0 | Drone hosts / on-prem infra (DOCKER_HOST_IP=<office-docker-host>, SigNoz) |
| User | 10.70.0.0/16 | 51823 | users0 | Dashboard users |
| Drone | 10.71.0.0/16 | 51824 | drones0 | Physical drones (Gateway resolves 10.71.x) |
jumphost
The single WireGuard EC2 instance (
<prod-ingress-ip>) that doubles as the platform’s jumphost and bridge to on-prem SITL. The Gateway can route rosbridge to a drone through it using x-drone-ip / x-drone-port headers (JUMPHOST_IP/JUMPHOST_PORT, default port 9090). It is the architectural keystone — and the primary single point of failure. See VPN IP Authentication & Jumphost Routing.Cloud Map service discovery
Production runs on ECS Fargate in AWS
eu-central-1. Each service registers a stable internal DNS name in the private hosted zone skyhub-prod.internal (e.g. gateway.skyhub-prod.internal, janus.skyhub-prod.internal, redis.skyhub-prod.internal, database.skyhub-prod.internal). Nearly everything — including PostgreSQL and Redis — is a self-hosted container discovered this way; there is no RDS and no ALB. See ECS Fargate Services.Video
Janus VideoRoom
Janus is the WebRTC SFU (Selective Forwarding Unit). The Gateway’s
VideoService creates one VideoRoom per drone (room id == drone.id, H264) and hands the drone the room_id/password/token over the rosbridge topic /video_room_details. The Dashboard subscribes to that room over WebRTC to watch the feed. See Janus WebRTC SFU and Janus Video Rooms.WHIP
WebRTC-HTTP Ingestion Protocol — the video ingest front door (Node.js server, REST
/whip, port 7080). The drone’s GStreamer pipeline whipsinks its H264 stream to /whip/endpoint/<id>; WHIP registers that as a publisher in the matching Janus VideoRoom over Janus’ WebSocket API (:8188). See WHIP Ingest Server and on-drone Video Streaming.Video is fully out-of-band from telemetry: drone → WHIP → Janus → Dashboard, never through the Gateway’s rosbridge path. ArUco precision-landing overlays are not burned into the video — they travel via Redis
{ip}:aruco_tracking and are drawn as a frontend canvas overlay.Manual control (gamepad)
WS Proxy (ws_proxy)
The cloud FastAPI relay (port
7070) that carries manual gamepad input on a separate, low-latency path from the Gateway’s rosbridge. It resolves the drone IP from PostgreSQL and publishes to Redis. See WebSocket Gamepad Proxy and Vehicle Commands & Gamepad.redispad
The preferred WS Proxy mode:
wss://{ws_proxy}/redispad/{droneId}?access_token=<jwt>. It decouples the client from the drone via Redis pub/sub ({drone_ip}:gamepad_input inbound, {drone_ip}:output / {drone_ip}:aruco_tracking outbound). The alternative /gamepad/{id} mode bridges directly to the drone’s onboard control socket. See Redis Message Bus.GUIDED velocity control
On the drone, gamepad axes become
SET_POSITION_TARGET_LOCAL_NED body-frame velocity setpoints — only when guided control is user-enabled and the vehicle is in GUIDED mode, with a 0.5 s dead-man timeout (zero velocity = hold). This deliberately replaces dangerous RC_CHANNELS_OVERRIDE. See the Safe Control model.On-drone autonomy
Isaac Visual SLAM
NVIDIA Isaac ROS Visual SLAM running on RealSense stereo + IMU, bridged into ArduPilot’s EKF as
VISION_POSITION_ESTIMATE for GPS-denied navigation. See Isaac Visual SLAM & Pose Bridge.ArUco landing / docking
Two distinct marker systems: e-ArUco precision landing for drones (
DICT_7X7_250, multi-level descent) and RealSense-D435 ArUco rover docking (marker id 70). See YOLO Detection, ArUco Landing & Docking.RTK NTRIP
Real-Time Kinematic GPS corrections: an NTRIP client receives RTCM and injects it into the FCU for centimeter-level positioning. See RTK NTRIP GPS Corrections.
SkyCore modules
The
core service loads feature modules (video_stream, aruco_landing, battery) via a ROS2-node ModuleLoader gated by <NAME>_ENABLED env vars. See Core & Gamepad Module Systems.Domain objects (Gateway data model)
execution
A record of one flight/mission run. Created on ARM (drone callback
POST /executions/start), completed on land/disarm, and enriched with uploaded assets (video, photos, .bin flight logs) that feed automated log analysis and reports. See Executions, Log Analysis & Reports.asset
A file produced by a flight — HLS video, images, or logs — stored in S3 (
skyhub-prod-assets) and linked to an execution/vehicle. Uploaded by the drone via presigned URLs. See S3 Assets, HLS Video & Execution Archives.mission
An ordered set of MAVLink waypoints.
MissionPoint rows are stored in MAVLink format and pushed to the drone via the /mavros/mission/push service; push_mission auto-prepends a synthetic TAKEOFF (command 22) if the first point isn’t already one. See Mission & Geofence MAVLink Format and Missions & Geofences API.geofence
A boundary the vehicle must respect —
polygon or circle, inclusion or exclusion. Distinct from a mission: geofences are pushed via the dedicated /mavros/geofence/push service and can be synced/cleared independently. Enabled geofences can be batch-synced to the FCU.| Command | Meaning |
|---|---|
| 16 | Waypoint (NAV_WAYPOINT) |
| 21 | Land |
| 22 | Takeoff |
| 20 | Return-to-launch (RTL) |
| 178 | Change speed |
The three auth models
Three distinct authentication schemes coexist across the stack. Knowing which one an endpoint uses tells you how to call it and how to secure it. See HTTP API Overview & Auth Models and Authentication & Security Model.| Model | Who | How | Where |
|---|---|---|---|
| JWT Bearer | Dashboard users | HS256 access token (10 min) + refresh (12 hr), Authorization: Bearer; Socket.IO reads it from ?token= | Almost all /api/v1/* UI routes |
| VPN source-IP trust | Drone / gamepad callbacks | check_vpn_ip decorator trusts a source IP starting with 10.71. (or an X-Drone-IP of SKYHUB_SITL_*) — no JWT | src/middleware/drone_vpn.py; activation pull, executions, asset uploads |
| Token / signature | Bootstrap, video, Stripe | 10-digit drone-activation header token, per-room video Bearer token, Stripe-Signature webhook verification | Drone activation, POST /video_room, POST /billing/webhook |
Overloaded names — disambiguate carefully
ws_proxy — two different things
ws_proxy — two different things
(1) The cloud service
skyhub_ws_proxy — a standalone FastAPI relay (port 7070) that carries gamepad input from the Dashboard, described above. (2) The on-drone ws_proxy docker-compose service — this is the SkyCore gamepad service (FastAPI WebSocket on 5001), the thing that actually turns operator input into MAVLink. They are named similarly and sit at opposite ends of the same control path. See Ecosystem WS Proxy vs Drone-OS Services.ModuleLoader — not one abstraction
ModuleLoader — not one abstraction
SkyCore’s
core service uses a ROS2-node ModuleLoader (env <NAME>_ENABLED flags). The gamepad and isaac-slam services each use their own plain loaders / DI containers. They share a name and an idea but are not the same code — do not merge them blindly. See Core & Gamepad Module Systems.type vs vehicle_type
type vs vehicle_type
A drone’s
type is physical or sitl (transport/lifecycle). Its vehicle_type is the airframe/rover class (copter/rover). Both are set at creation (POST /api/v1/drone). Don’t conflate them.Video ports — 7080 vs 8188
Video ports — 7080 vs 8188
WHIP’s REST ingest is on
7080; Janus’ WebSocket API (what WHIP and the Dashboard drive) is on 8188. They are separate services on separate ports in the same video pipeline.Where to go next
Local Dev Quickstart
Run the Gateway and a SITL drone on your machine.
Repository Map
Which repo owns what, and who maintains it.
Cross-System Flows
Follow a command and a telemetry frame end-to-end.

