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
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)
skyhub_ws_proxy is a FastAPI relay on :7070 (skyhub_ws_proxy/main.py) with two modes:
ws /gamepad/{drone_id}— legacy direct bridge tows://{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}:outputand{drone_ip}:aruco_tracking.
skyhub container, listening on GAMEPAD_WS_PORT — 5001 is the default, but the proxy hardcodes 5001, so a vehicle that overrides it is unreachable in direct mode.
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.
Simulation — SITL
skyhub_sitl is a dockerized ArduPilot SITL stack that stands in for a physical drone. The Gateway runs it as three containers: the ArduPilot SITL container (sim_vehicle.py under supervisord), plus two containers from the same skyhub-app image — SKYHUB_ROLE=core (MAVROS over UDP 14550 + rosbridge_server on :9090) and SKYHUB_ROLE=agent. They present the same rosbridge/topic + WHIP video contract as a real vehicle, so the Gateway treats SITL and physical 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.
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
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.

