skyhub_core repository and turns an NVIDIA Jetson plus an ArduPilot/Pixhawk flight controller and a SIYI gimbal camera into an internet-connected, AI-capable drone or rover. Everything is packaged as a set of docker-compose services, all running with network_mode: host and restart: always, glued together by three interconnect fabrics: MAVLink (routed by a single mavp2p hub), ROS 2 DDS, and a Redis message bus.
From the cloud’s perspective, the drone is reached at exactly one control endpoint — rosbridge on port 9090 — which the Gateway Service connects to over a per-drone WireGuard VPN. This page is the map of what runs behind that port. Deep-dives on each area live in the sibling pages linked throughout.
Two CLIs with similar names do different jobs:
skycore(installer/sc.sh) is the vehicle-side install and lifecycle tool —skycore install,activate,up,status,version,update.skycore_cli.pyat the repo root is not the OS entrypoint or service orchestrator. It is a host-run ArduPilot maintenance CLI (nav source, params, log/terrain download, camera SD).
docker-compose profiles plus supervisord inside the skyhub container. See the SkyCore CLI Reference.The three services
The production topology is defined indocker-compose.yml. Each service is gated by a Compose profile, and COMPOSE_PROFILES selects which ones start on boot.
| Service (compose name) | Profile | What it does | Detail page |
|---|---|---|---|
skyhub | skyhub | One image, both halves, selected by SKYHUB_ROLE (vehicle | core | agent). The ROS half: MAVROS + rosbridge (:9090) + drone_node, which loads the video_stream, aruco_landing, and battery modules. The agent half: a WebSocket (:5001 /gamepad) for manual control, safe GUIDED velocity, gimbal/zoom, and arm/disarm asset upload (HLS video, photos, logs) to S3. | Services · Module System · Safe Control |
mavproxy | mavproxy | The mavp2p MAVLink router. Bridges the FC serial link to a fixed UDP port map for every other consumer. | MAVLink Routing |
rtk-ntrip | rtk | NTRIP client that injects RTCM corrections into the FC for RTK-fixed GPS and sends GGA back to the caster. | RTK NTRIP |
.env.example is COMPOSE_PROFILES=skyhub,mavproxy,rtk; skycore activate defaults to skyhub,mavproxy.
System topology
The three interconnect fabrics
Every service talks to the others over one of exactly three buses. Understanding which one carries what is the fastest way to trace a bug.MAVLink via mavp2p
A single router owns the FC serial port and fans MAVLink out over UDP. The port map is load-bearing.
ROS 2 DDS
Shared
ROS_DOMAIN_ID over host networking. Carries /mavros/* topics and the video-room string topics.Redis pub/sub
Per-drone
{ip}:channel command and status bus, keyed by the drone’s WireGuard IP.1. MAVLink — the mavp2p hub
mavproxy runs the mavp2p router. It is the single owner of the flight-controller serial link and fans MAVLink out over a fixed UDP port map that every other consumer must agree on (docker-compose.yml:184):
docker-compose.yml (mavproxy command)
| UDP port | Consumer |
|---|---|
14550 | skyhub MAVROS (FCUURL=udp://127.0.0.1:14550@) |
14777 | skyhub agent MAVLink handler (MAVLINK=udpout:127.0.0.1:14777) |
14560 | rtk-ntrip RTCM injection |
14561 | skyhub ArUco-landing direct velocity control |
14900 | External GCS (e.g. QGroundControl over the VPN) |
2. ROS 2 DDS
MAVROS publishes the/mavros/* topic tree and drone_node consumes /mavros/state — over DDS on a shared ROS_DOMAIN_ID (default 1, per-vehicle configurable) across the host network. drone_node (docker/core/main.py:67) also carries the Janus video-room config as std_msgs/String topics (video_room_details, video_room_state, video_room_source), which the Gateway publishes over rosbridge.
3. Redis message bus
The manual-control and status plane is Redis pub/sub. Channels are namespaced by the drone’s WireGuardwg0 IP — {ip}:gamepad_input, {ip}:output, {ip}:chat, {ip}:aruco_tracking, and more — so one Redis can serve many drones without cross-talk. Two channels are deliberately global (unprefixed): video_stream_state and video_stream_status_request. The channel catalog and dual command path (Redis relay vs. direct WebSocket) are documented in the Message Bus page.
If the
wg0 interface can’t be resolved (e.g. local dev with no VPN), you must set IP_OVERRIDE or the agent half raises RuntimeError at boot. The legacy misspelling IP_OVRIDE is still honored for backwards compatibility.How the cloud attaches
The Gateway never speaks MAVLink or Redis directly to a drone in the command path — it attaches at rosbridge:9090 and pub/subs ROS topics and calls ROS services. From there:
- Commands (arm, takeoff, set-mode, missions) become rosbridge
call_service/publishframes → MAVROS → mavp2p → the FC. See the Drone Control Service. - Telemetry flows the reverse way: MAVROS topics → rosbridge → Gateway → Socket.IO rooms → the Dashboard. Note
set_stream_rates.shmust raise the FC stream rate to 10 Hz first, or sensor topics stay empty. - Manual gamepad control takes a separate low-latency path: Dashboard → WS Proxy → Redis
{ip}:gamepad_input→ the drone’s agent half onGAMEPAD_WS_PORT(default5001), not through rosbridge. See Vehicle Commands. - Video is fully out-of-band: the on-drone GStreamer pipeline whipsinks H264 to the WHIP server, which registers a Janus VideoRoom publisher the Dashboard subscribes to over WebRTC. See Video Streaming.
Service boot and dependencies
docker-compose up reads COMPOSE_PROFILES and starts the selected services. All are network_mode: host and restart: always. Startup order is delay-based, not health-gated (except in the SITL overlay): each service sleeps <SERVICE>_STARTDELAY seconds before starting (DRONE_STARTDELAY=5, RTK_STARTDELAY=10, etc.), reinforced by depends_on — skyhub and rtk-ntrip both depend on mavproxy, and nothing else has an edge.
Inside the skyhub container there is a second init layer: supervisord (docker/skyhub/supervisord.conf) is PID-1-like and launches six processes in priority order. Which ones start is decided by SKYHUB_ROLE. drone_node starts last (priority 999) so MAVROS, rosbridge, and stream rates are ready before it subscribes.
Gotchas a future editor must preserve
network_mode: hosteverywhere. There is no Docker port isolation — ports9090,5001,322, and the145xxMAVLink ports are on the host. All three application ports are env-configurable (ROSBRIDGE_PORT,GAMEPAD_WS_PORT,SKYHUB_SSH_PORT) because containers can share a host network namespace. Moving to bridged networking would break both DDS discovery and the127.0.0.1MAVLink loopback assumptions.mavp2pis a single point of failure and un-configurable at runtime (endpoints are CLI args). The port map above is load-bearing.- Two independent module systems share the names
ModuleLoader/ModuleBase: the ROS 2 env-registry loader in the ROS half vs. the explicitly-registered loader in the agent half. They now live in one image — ROS half at/, agent at/app— kept apart bysys.path, not by a container boundary. They are different code; do not merge them. See Module System. - Movement is GUIDED-mode
SET_POSITION_TARGET_LOCAL_NED, notRC_CHANNELS_OVERRIDE. Zero velocity means hold, and control only activates when user-enabled and the vehicle is in GUIDED, with a dead-man command-timeout watchdog. All three guards are safety-critical — see Safe Control. - The video overlay is intentionally not burned into the stream (the “canvas approach”): ArUco tracking goes to Redis
{ip}:aruco_trackingfor a frontend canvas overlay, to save Jetson GPU and preserve video quality.setup_video_overlay()is a deliberate no-op.
Local development
There is a hardware-free path:docker-compose.local.yml overlays an ArduPilot SITL container and a local Redis (10.223.0.2) on a bridge network that mimics the VPN (10.223.0.0/16), parks the hardware services (mavproxy, rtk-ntrip) on a disabled profile, switches to the TEST camera and a software encoder, and injects IP_OVERRIDE so Redis channels resolve without wg0.
sitl.sh selects the vehicle type and home location.
Where to go next
Microservices & Container Profiles
Per-service reference: env vars, network mode, and startup ordering.
Redis Message Bus & WebSocket Interfaces
The full channel catalog and the dual command path.
MAVLink Routing (mavp2p)
The port map and flight-control link in detail.
Guided Velocity Control & Safety Model
The safe manual-flight control that replaced RC override.
e-ArUco Precision Landing
Marker-guided descent over the SIYI down-camera.
SkyCore CLI Reference
The host-run ArduPilot maintenance tool and the
skycore installer CLI.
