SkyHub is not one program — it is a fleet of repositories arranged around a single control-plane hub, the Gateway Service. The Dashboard talks to the Gateway over HTTP + Socket.IO; the Gateway brokers commands and telemetry to drones over rosbridge WebSocket (port 9090), whether those drones are physical (SkyCore Drone OS) or SITL Docker containers it spawns. Everything else in the platform is a satellite service that handles one plane of responsibility the Gateway deliberately does not own end-to-end: live video, network isolation, low-latency manual control, shared storage, and the physical ground hardware. This page is the map of those satellites — what each repo is, which plane it serves, and how it wires into core/gateway/dashboard by port and protocol. Deep-dives live in the sibling pages linked throughout. For the request-level walkthroughs (command path, telemetry, video, gamepad), see Cross-System Data Flows; for the repo inventory and ownership, see Repository Map.

The four planes

Control plane

Two independent paths reach a drone: the Gateway’s rosbridge channel (:9090, telemetry + flight/mission commands) and the WS Proxy (:7070 → drone :5001, manual gamepad). They do not share a socket.

Video plane

Out-of-band WebRTC. On-drone GStreamer publishes H264 to WHIP (:7080), which registers a Janus VideoRoom publisher (:8188); the Dashboard subscribes over WebRTC.

Network plane

The User VPN service runs three WireGuard planes and writes iptables rules so a user can only reach drones they own. This is how a physical drone’s 10.71.x address becomes routable.

Shared infra

PostgreSQL (user/drone/user_drone_access), Redis (pub/sub bus), and S3 (assets, VPN configs) are touched by the Gateway, WS Proxy, and User VPN alike.

Platform map

Role-by-repo table

RepoPlane / roleTechConnects viaDeep-dive
skyhub_gateway_serviceControl-plane hubFlask + Socket.IOHTTP :5000, Socket.IO, rosbridge :9090, Docker API/gateway/overview
skyhub_janusVideo SFU (WebRTC)C (meetecho janus-gateway)WS API :8188, WebRTC media/ecosystem/janus
skyhub_whipVideo ingest (WHIP)Node.js / ExpressREST :7080/whip, Janus WS :8188/ecosystem/whip
skyhub_sitlDrone simulatorArduPilot SITL + ROS2 + supervisordrosbridge :9090, MAVROS UDP 14550, WHIP/ecosystem/sitl
skyhub_user_vpnNetwork isolationPython + WireGuard + iptablesWireGuard 51822/51823/51824, status API :5050/ecosystem/user-vpn
skyhub_ws_proxyGamepad relayFastAPI / uvicornWS :7070 → drone :5001 or Redis pub/sub/ecosystem/ws-proxy
skyhub_nexusBattery-swap airhub (hardware)Python + Arduino + pyusbUSB serial /dev/ttyACM0, IMAX USB/ecosystem/nexus-and-vehicles
skyhub_roverGround robot firmwareESP32 / ArduinoWiFi AP/STA, ESP-NOW, gamepad :5001/ecosystem/nexus-and-vehicles
skyhub_ugvGround base firmwareESP32 / PlatformIOWiFi, gamepad WS / JSON/ecosystem/nexus-and-vehicles
skyhub_observerPlanned (stub)undefined/ecosystem/planned-services
skyhub_probePlanned (stub)would run SkyCore/ecosystem/planned-services
skyhub_carrierPlanned (stub)undefined/ecosystem/planned-services

Video plane — Janus + WHIP

Janus (skyhub_janus) is the Selective Forwarding Unit built from meetecho’s janus-gateway C source. It exposes a WebSocket API on :8188 (used by WHIP to drive VideoRooms; skyhub_janus/templates/janus.transport.websockets.jcfg), with ICE via Google STUN stun.l.google.com:19302 (set into janus.jcfg via skyhub_janus/Dockerfile:87-88). The Gateway owns rooms: VideoService creates a VideoRoom via JANUS_URL and hands room_id / password / token to the drone over the /video_room_details rosbridge topic. The Dashboard subscribes to the same room over WebRTC (App State & Video). WHIP (skyhub_whip) is the ingest front door — a Node.js fork of meetecho’s simple-whip-server. It binds the WHIP REST API on :7080 under /whip (skyhub_whip/src/config.js:12), not the 3000 its Dockerfile EXPOSEs — a common source of confusion. A drone or SITL GStreamer pipeline whipsinks its H264 stream to POST /whip/endpoint/<id>, and WHIP maps that ingest to a Janus VideoRoom publisher over ws://127.0.0.1:8188 (JANUS_ADDRESS). See Video Streaming for the on-drone side.
ArUco precision-landing markers are not burned into the video. They travel over Redis {ip}:aruco_tracking and are rendered as a frontend canvas overlay, so the WebRTC stream stays clean.

Network plane — User VPN

skyhub_user_vpn is a Python/Flask manager running with NET_ADMIN in host-network mode. On one host it runs three distinct WireGuard planes (skyhub_user_vpn/docker-compose.yml): core 10.69.0.0/16:51822 (iface wg0), user 10.70.0.0/16:51823 (users0), and drone 10.71.0.0/16:51824 (drones0). It reads the shared PostgreSQL user / drone / user_drone_access tables to generate peer configs (synced to the skyhub-{env}-user-vpn S3 bucket) and iptables allow-rules so a user’s traffic reaches only the drones they own (skyhub_user_vpn/sql_4_iptables.sql). A small status/firewall API is served on :5050 — the Gateway reads it via VPN_SERVICE_IP / VPN_SERVICE_PORT, then resolves a drone’s 10.71.x address in src/middleware/drone_vpn.py to open rosbridge. Details in User VPN & Network Isolation and Network Topology.

Control plane — WS Proxy (parallel to rosbridge)

There are two independent control paths to a drone. Flight/mission commands and telemetry go through the Gateway’s rosbridge (:9090). Manual gamepad control goes through the WS Proxy (:7070 → drone :5001). A change to one does not affect the other — do not assume gamepad traffic passes through the Gateway.
skyhub_ws_proxy is a FastAPI relay on :7070 (skyhub_ws_proxy/main.py) with two modes:
  • ws /gamepad/{drone_id} — legacy direct bridge to ws://{drone_ip}:5001/gamepad.
  • ws /redispad/{drone_id} — preferred; decouples client from drone via Redis pub/sub: inbound {drone_ip}:gamepad_input, outbound {drone_ip}:output and {drone_ip}:aruco_tracking.
It also serves GET /drone/{drone_id} (id, user_id, ip) and resolves drone IPs from the same PostgreSQL drone table. The Dashboard side is Vehicle Commands & Gamepad; the Redis channel contract is catalogued in Redis Channels & MAVLink Port Map.
Known auth gap: the WS Proxy gamepad endpoints have no JWT/ownership check yet (TODO in main.py). Treat the control channel as trusted-network-only until closed.

Simulation — SITL

skyhub_sitl is a dockerized ArduPilot SITL + ROS2 Humble stack that stands in for a physical drone. Each container runs (via supervisord) sim_vehicle.py, MAVROS over UDP 14550, rosbridge_server on :9090, and a GStreamer→WHIP video node — presenting the exact same rosbridge/topic + WHIP video contract as SkyCore, so the Gateway treats SITL and real drones identically. Containers are named SKYHUB_SITL_<n> with port 9090+n and ROS_DOMAIN_ID=n isolation; WHIP_SERVER_URL defaults to http://172.17.0.1:7080. The Gateway spawns and connects to them via the Docker API on DOCKER_HOST_IP — see SITL Simulator and the orchestration in SITL Drone Lifecycle.

Ground hardware — Nexus, Rover & UGV

Nexus (skyhub_nexus) is the “airhub”: an autonomous battery-swap and charging station. Python drives an Arduino over USB serial (/dev/ttyACM0, skyhub_nexus/main.py:5) to actuate the swap mechanism and controls an IMAX B6 charger over USB (pyusb, imax_usb.py) for LiPo charge/discharge cycles. It is standalone hardware — not wired into the Gateway control plane in-repo; it operates the pad drones dock to. Rover (skyhub_rover) and UGV (skyhub_ugv) are ESP32 firmware for WaveShare-class ground robots — WiFi AP/STA, ESP-NOW, IMU/servos, and an onboard gamepad control server on :5001. When integrated they are driven through the WS Proxy gamepad channel like a drone, or standalone via their own AP web UI.
skyhub_rover must build against ESP32 Arduino core exactly 2.0.17 — the 3.x core breaks the build (skyhub_rover/OUR_ROVER/README.md:1).
All three are covered in Nexus AirHub & Ground Vehicle Firmware.

Planned / stub repos

skyhub_observer, skyhub_probe, and skyhub_carrier are placeholders — README + generic dev-principles only, no source yet. Their intent is inferred from their names: Observer (platform observability / spectator), Prob-E (a hybrid air+ground vehicle that would run SkyCore and rides on the Carrier), and Carrier (a ground transport base for Prob-E). Document them as planned, never as running services — see Planned / Stub Repos.

Port reference

PortServiceProtocolConsumed by
5000GatewayHTTP + Socket.IODashboard
5001Drone gamepad serverWebSocketWS Proxy (direct mode)
5050User VPN status APIHTTPGateway
5432PostgreSQLTCPGateway, WS Proxy, User VPN
6379Redispub/subGateway, WS Proxy
7070WS ProxyWebSocket + HTTPDashboard
7080WHIPHTTP /whipDrone / SITL GStreamer
8188JanusWebSocket APIWHIP, Dashboard (media)
9090rosbridgeWebSocketGateway
14550MAVROS ↔ SITLUDP (MAVLink)in-container only
51822/51823/51824User VPN (core/user/drone)WireGuarddrones, users
For the transport-level view of every real-time channel (Socket.IO, rosbridge, WebRTC, Redis), see Real-time Transport Channels. For the big-picture architecture, see Platform Architecture Overview.