Constellation OverwatchConstellation Overwatch
Integrations

Pulsar

Edge sync agent for GCS-in-a-Box — fleet registration, MAVLink relay, video bridge, and on-device detection in a single binary

Edge sync agent that connects ground control stations to Constellation Overwatch. Single 14MB static binary, zero config to first telemetry.

Pulsar v0.0.1-beta is the initial public release. Fleet registration, MAVLink relay, and video bridging are production-ready. On-device detection requires CGO and is build-tag gated.

About

Pulsar is the edge companion to Constellation Overwatch. It runs on a GCS device (laptop, NUC, Raspberry Pi) and handles everything between your fleet assets and the Overwatch data plane:

  • Auto-registers entities with the Overwatch API from a declarative YAML config
  • Relays MAVLink telemetry over NATS JetStream with per-entity subject routing
  • Bridges video streams via RTSP with WebRTC/HLS support through MediaMTX
  • Runs YOLO detection on-device with bounding-box overlay (optional, build-tag gated)
  • Syncs continuously — watches config for changes every 30s, reconciles without restarts

Highlights

  • Guided first-time setup — interactive terminal wizard generates fleet config on first boot
  • Declarative fleet config — define entities in YAML, Pulsar handles registration and reconciliation
  • Idempotent registration — entity UUIDs tracked across restarts, no duplicates
  • MAVLink relay — 1:1 UDP listeners per entity with auto-assigned sequential ports
  • NATS JetStream publishing — telemetry envelopes on constellation.telemetry.{entity_id}.{msg_type}
  • KV state aggregation — per-entity device state merged by message type
  • RTSP video bridge — per-entity video relay with MediaMTX auto-detection or embedded fallback
  • On-device detection — YOLO26 ONNX inference at 15fps with bounding-box overlay
  • Docker ready — multi-stage Alpine image, CGO_ENABLED=0 for standard builds
  • Cross-platform — Linux and macOS (amd64, arm64)

Prerequisites

Quick Start

Installation

# Clone
git clone https://github.com/Constellation-Overwatch/pulsar.git
cd pulsar

# Configure credentials
cp .env.example .env
# Edit .env with your Overwatch API key and NATS nkey

# Run (guided setup on first boot if no fleet.yaml exists)
task dev

Docker Deployment

# Build and run full stack (Pulsar + MediaMTX + TAK Server)
docker compose up -d

# Or build standalone
task docker-build
docker run --env-file .env -v $(pwd)/config:/app/config pulsar:latest

Configuration

Environment Variables

VariableRequiredDefaultDescription
C4_API_KEYYesOverwatch API bearer token
C4_BASE_URLYesOverwatch API URL
C4_NATS_KEYYesNATS nkey seed for JetStream auth
C4_NATS_URLNonats://localhost:4222NATS server URL
MAVLINK_BASE_PORTNo14550Starting port for auto-assigned MAVLink listeners
RTSP_HOSTNolocalhostHostname for local RTSP connections
ADVERTISE_HOSTNoAuto-discoveredHostname/IP published to Overwatch for external consumers
MEDIAMTX_API_URLNohttp://localhost:9997MediaMTX API URL for RTSP server auto-detection

Fleet Config

Define your fleet in config/fleet.yaml:

organization:
  name: "GCS Alpha Station"
  type: "civilian"
  description: "Rapid response ground control station"

entities:
  - name: "Primary UAV"
    type: "uav"
    priority: "high"
    status: "active"
    mavlink: true                # auto-assign port from MAVLINK_BASE_PORT
    video_config:
      protocol: "rtsp"
      port: 8554
      source: "rtsp://admin:[email protected]:554/cam1"

  - name: "Ground Camera"
    type: "isr_sensor"
    priority: "normal"
    status: "active"
    video_config:
      protocol: "rtsp"
      port: 8554
      device: "/dev/video0"     # local capture device

Entity types: uav, fixed_wing, vtol, helicopter, airship, ground_vehicle, boat, isr_sensor, camera, gcs

Reconciliation

Pulsar reconciles desired state (fleet.yaml) against actual state (c4.json) on every boot and sync cycle:

fleet.yamlc4.json
RoleDesired stateActual state
AuthorHumanMachine
GitCommittedGitignored
ContainsNames, types, mavlink: trueUUIDs, resolved ports, RTSP URLs

Entity names are used to track UUIDs across restarts. Drift in priority, status, or type triggers automatic updates on Overwatch. Entities removed from config are cleaned up.

Architecture

Service Modules

ServicePackageDescription
Registrypkg/services/registry/Overwatch API registration and reconciliation
Relaypkg/services/relay/Per-entity MAVLink UDP listeners and frame parsing
Publisherpkg/services/publisher/NATS JetStream publishing and KV state aggregation
Videopkg/services/video/RTSP server, video bridge, detection overlay
Detectorpkg/services/detector/YOLO26 ONNX inference (build-tag gated)

NATS Integration

Subject Hierarchy

constellation.telemetry.{entity_id}.heartbeat
constellation.telemetry.{entity_id}.globalpositionint
constellation.telemetry.{entity_id}.attitude
constellation.telemetry.{entity_id}.vfr_hud
constellation.telemetry.{entity_id}.systemstatus

Consuming Messages

# Subscribe to specific entity
nats sub "constellation.telemetry.{entity_id}.>"

# Subscribe to all heartbeats
nats sub "constellation.telemetry.*.heartbeat"

# Subscribe to all telemetry
nats sub "constellation.telemetry.>"

KV Store

Bucket: CONSTELLATION_GLOBAL_STATE Key pattern: {entity_id}.mavlink

Device state is aggregated — each message type merges into the existing state rather than overwriting it. This means the KV entry always reflects the latest value from every message type received.

Telemetry Envelope

{
  "drone_id": "8383e1cc-7dcd-4a51-af80-58ec884bf407",
  "source": "Primary UAV",
  "timestamp_relay": "2026-02-27T16:42:13Z",
  "msg_id": 0,
  "msg_name": "Heartbeat",
  "fields": {
    "type": 2,
    "autopilot": 3,
    "base_mode": 89,
    "custom_mode": 4,
    "system_status": 4
  }
}

Video Pipeline

Pulsar supports two video source types per entity:

SourceConfig KeyDescription
Network RTSPsourceProxied through MediaMTX or read by detector
Local DevicedeviceCaptured via OpenCV, encoded to H264

Host Configuration

Pulsar uses separate hosts for local and external video URLs:

VariablePurpose
RTSP_HOSTWhere local services (bridge, detector) connect
ADVERTISE_HOSTWhere external consumers (Overwatch UI) connect

The registry generates advertised endpoints from ADVERTISE_HOST:

stream_url:   rtsp://{ADVERTISE_HOST}:{port}/{entity_id}
overlay_url:  rtsp://{ADVERTISE_HOST}:{port}/{entity_id}/pulsar
webrtc_url:   http://{ADVERTISE_HOST}:8889/{entity_id}/pulsar
hls_url:      http://{ADVERTISE_HOST}:8888/{entity_id}/pulsar

Detection Overlay

When built with -tags detection, Pulsar runs YOLO26 ONNX inference on the video stream and publishes an overlay feed at the /pulsar path suffix. Detection runs every 5th frame with cached results drawn on all frames for smooth 15fps output.

# Build with detection
task build:detection

# Run with detection (needs ONNX Runtime + OpenCV)
MODEL_PATH=data/yolo26s.onnx task dev:detection

Ports are auto-assigned sequentially from MAVLINK_BASE_PORT (default 14550):

EntityConfigResolved Port
Primary UAVmavlink: true:14550
Secondary UAVmavlink: true:14551
Fixed Overwatchmavlink: {port: 14560}:14560
Ground Camera(no mavlink key)(no listener)

Explicit ports are reserved first, then auto-assigned ports fill sequentially, skipping conflicts.

Project Structure

pulsar/
├── cmd/microlith/main.go          # Entry point, guided setup, sync loop
├── pkg/services/
│   ├── registry/                  # Overwatch API registration
│   ├── relay/                     # MAVLink UDP listeners
│   ├── publisher/                 # NATS JetStream + KV publishing
│   ├── detector/                  # YOLO26 ONNX inference (build-tag gated)
│   ├── video/                     # RTSP server, bridge, overlay
│   └── logger/                    # Zap-based structured logging
├── pkg/shared/                    # Config types, HTTP client, network utils
├── internal/x264-go/              # Local fork (WebRTC IDR keyframe fix)
├── config/fleet.yaml              # Fleet config (user-authored)
├── Dockerfile                     # Multi-stage Alpine build
└── Taskfile.yml                   # Build commands

For full documentation, configuration reference, and architecture details, see the Pulsar README or the release notes.

On this page