Skip to content

TALOS Architecture

System Overview

TALOS (Tracking, Acquisition, and Link Operation System) is a distributed satellite ground station controller. It coordinates multiple antenna stations in real time from a centralized mission control interface, running SGP4 orbital propagation at 2 Hz with Doppler correction.

The system consists of five core components connected by an MQTT message bus, with an optional streaming pipeline to external YAMCS mission control instances:

Components

Core API (core/main.py) FastAPI web application serving the mission control dashboard, REST API, and SatNOGS data synchronization. Handles user authentication (signed session cookies), organization management, campaign CRUD, station provisioning, and satellite catalog maintenance. All API endpoints are org-scoped with role-based access control. Communicates with the Director and Agents via MQTT, and persists state to PostgreSQL (managed by Alembic migrations).

Director (director/mission_director.py) Real-time physics engine running a 2 Hz control loop. Reads active campaign assignments from the database, performs SGP4 orbital propagation via Skyfield for each assigned satellite, computes topocentric coordinates (azimuth, elevation) for the assigned station, calculates Doppler-corrected frequencies, predicts upcoming passes, and publishes pointing and tuning commands to agents over org-scoped MQTT topics. Supports concurrent multi-satellite tracking -- different stations can track different satellites simultaneously. Runs as a separate process from the Core API.

Agent (agent/agent.py) Lightweight edge client deployed on ground station hardware (typically Raspberry Pi). Subscribes to MQTT command topics for its station, translates commands into Hamlib protocol calls, and forwards them to rotctld (antenna rotator) and rigctld (radio receiver) over TCP. Reports station telemetry back to the Director. Additionally manages GNU Radio flowgraph subprocesses for signal demodulation, collecting decoded frames over local ZMQ and publishing them to MQTT for the stream router.

Stream Router (core/stream_router.py) Background MQTT subscriber running within the Core process. Receives decoded frames from agents, looks up the campaign's MissionLink configuration, and forwards frames to the appropriate external YAMCS instance via UDP. Follows the phasma-operations convention of prepending a 4-byte frame identifier to each datagram.

MQTT Broker (ops/mosquitto/) Eclipse Mosquitto message broker providing pub/sub communication between all components. Supports both TCP (port 1883) and WebSocket (port 9001) listeners. Configured with username/password authentication and topic ACLs.

PostgreSQL Database Stores organizations, memberships, user accounts, campaigns, per-station assignments, ground station definitions (location, capabilities, API keys), and the satellite catalog (metadata, transmitter info, TLE cache synced from SatNOGS DB). Schema is versioned and managed by Alembic migrations.


Data Flow

                    +---------------------+
                    |      Browser        |
                    |  (Mission Control   |
                    |    Dashboard)       |
                    +---------+-----------+
                              |
                       HTTP / WebSocket
                              |
                    +---------v-----------+
                    |     Core API        |
                    |     (FastAPI)       |
                    +--+------+------+----+
                       |      |      |
              DB read/ |      |      | MQTT publish/subscribe
              write    |      |      | (system events, viz relay)
                       |      |      |
              +--------v--+   |      +----------+
              | PostgreSQL |   |                 |
              +--------+--+   |                 |
                       |      |                 |
              DB read  |      |                 |
                       |      |                 |
              +--------v------v------+          |
              |      Director        |<---------+
              |   (Physics Engine)   |
              +--------+-------------+
                       |
              MQTT publish (az/el, freq, session, viz)
                       |
          +------------+-------------+
          |            |             |
   +------v----+ +-----v-----+ +----v------+
   |  Agent 1  | |  Agent 2  | |  Agent N  |
   +------+----+ +-----+-----+ +----+------+
          |             |            |
     TCP (Hamlib)  TCP (Hamlib)  TCP (Hamlib)
          |             |            |
   +------v----+ +-----v-----+ +----v------+
   | rotctld / | | rotctld / | | rotctld / |
   | rigctld   | | rigctld   | | rigctld   |
   +-----------+ +-----------+ +-----------+
          |             |            |
      Hardware      Hardware     Hardware
      (Rotator,     (Rotator,    (Rotator,
       Receiver)     Receiver)    Receiver)

Flow Description

  1. User opens the dashboard in a browser, manages their organization, creates campaigns, and assigns stations.
  2. Core API writes configuration to PostgreSQL, publishes talos/system/refresh to notify the Director.
  3. Director reads active campaign assignments from PostgreSQL, runs the SGP4 propagation loop at 2 Hz for each assigned satellite, and publishes pointing commands (cmd/rot), tuning commands (cmd/rig), session control (cmd/session), and visualization data (mission/viz) to org-scoped per-station topics.
  4. Agents subscribe to their station-specific command topics, translate commands to Hamlib TCP protocol, and forward to rotctld/rigctld. Agents publish telemetry back on telemetry/rot.
  5. Dashboard receives visualization data via the Core API (WebSocket relay) for multi-campaign satellite tracking display with color-coded satellite identification.

MQTT Topic Hierarchy

All MQTT topics use the talos/ prefix. As of v0.2, topics support org-scoped prefixes: talos/{org_slug}/gs/{station_id}/.... Legacy non-org-scoped topics (talos/gs/{station_id}/...) are still supported for backward compatibility. Topic definitions live in shared/topics.py.

System Topics

Topic Direction QoS Payload Schema Description
talos/system/refresh Core -> Director, Dashboard 1 SystemRefresh System event broadcast (sync complete, campaign change, station update)
talos/director/heartbeat Director -> All 0 DirectorHeartbeat Periodic heartbeat proving the physics loop is alive

Org-Scoped Station Control Topics

Topic Direction QoS Payload Schema Description
talos/{org_slug}/gs/{station_id}/info Agent -> Director 1 (retained) StationInfo Station handshake and registration announcement
talos/{org_slug}/gs/{station_id}/cmd/config Director -> Agent 1 (retained) StationConfig Station configuration (rotator/rig addresses, capabilities)
talos/{org_slug}/gs/{station_id}/cmd/session Director -> Agent 1 SessionCommand Start/stop a tracking session for a satellite pass
talos/{org_slug}/gs/{station_id}/cmd/rot Director -> Agent 0 RotatorCommand Rotator position command (azimuth, elevation in degrees)
talos/{org_slug}/gs/{station_id}/cmd/rig Director -> Agent 0 RigCommand Radio tuning command (frequency in Hz, modulation mode)
talos/{org_slug}/gs/{station_id}/schedule Director -> Agent 1 PassSchedule Upcoming pass schedule for this station

Telemetry Topics

Topic Direction QoS Payload Schema Description
talos/{org_slug}/gs/{station_id}/telemetry/rot Agent -> Director 0 RotatorTelemetry Actual rotator position readback (az, el)

Visualization Topics

Topic Direction QoS Payload Schema Description
talos/{org_slug}/mission/viz Director -> Dashboard 0 MissionViz Satellite position, footprint, ground track for live display (multi-campaign)
talos/{org_slug}/mission/select Dashboard -> Director 1 TransmitterSelect Manual transmitter selection (frequency/mode change)

Subscription Wildcards

Pattern Subscriber Purpose
talos/{org_slug}/gs/{station_id}/cmd/# Agent All commands for a specific station
talos/{org_slug}/gs/+/info Director Registration from any station in the org
talos/{org_slug}/gs/+/telemetry/rot Dashboard Telemetry from any station in the org
talos/{org_slug}/gs/+/schedule Dashboard Schedule updates from any station in the org

Legacy Topics (Deprecated)

Non-org-scoped topics (e.g., talos/gs/{station_id}/cmd/rot) are still accepted for backward compatibility but will be removed in a future release. All new deployments should use org-scoped topics.


Database Schema

The PostgreSQL schema is defined via SQLModel ORM in core/database.py and managed by Alembic migrations (see migrations/). All tables use auto-incrementing integer primary keys unless otherwise noted.

Entity Relationship

  +--------------+        +------------+
  | Organization |        |    User    |
  +--------------+        +------------+
  | id (PK)      |        | id (PK)    |
  | name         |        | email      |
  | slug (unique)|        | created_at |
  | created_at   |        +-----+------+
  +------+-------+              |
         |                      |
         |    +------------+    |
         +--->| Membership |<---+
         |    +------------+
         |    | id (PK)    |
         |    | user_id (FK)
         |    | org_id (FK)|
         |    | role       |   (owner / operator / viewer)
         |    | created_at |
         |    +------------+
         |
         +---> Station (org_id FK)
         |
         +---> Campaign
                | id (PK)
                | org_id (FK)
                | name, norad_id, satnogs_id
                | priority, status, transmitter_uuid
                |
                +---> Assignment
                      | id (PK)
                      | campaign_id (FK)
                      | station_id (FK)
                      | status (active/paused/completed)


  +----------------+       +----------------+
  | SatelliteCache |       |  Transmitter   |
  +----------------+       +----------------+
  | sat_id (PK)    |<------| uuid (PK)      |
  | norad_id       |  1:N  | sat_id (FK)    |
  | name           |       | description    |
  | tle_json       |       | downlink_low   |
  | updated_at     |       | mode, alive    |
  +----------------+       +----------------+

Tables

Organization

Column Type Constraints Description
id Integer PK, auto-increment Organization identifier
name String Not null Organization display name
slug String Unique, not null URL-safe identifier (used in API paths and MQTT topics)
created_at DateTime Default: now Creation timestamp

A default organization is auto-created for new users who do not belong to any organization.

Membership

Column Type Constraints Description
id Integer PK, auto-increment Membership identifier
user_id Integer FK -> User, not null Member user
org_id Integer FK -> Organization, not null Parent organization
role String Not null, default: operator Role: owner, operator, or viewer
created_at DateTime Default: now Membership creation timestamp

Unique constraint on (user_id, org_id) prevents duplicate memberships.

User

Column Type Constraints Description
id Integer PK, auto-increment User identifier
email String Unique, not null User email address
created_at DateTime Default: now Account creation timestamp

Station

Column Type Constraints Description
id Integer PK, auto-increment Internal station identifier
station_id String Unique, not null MQTT identifier (e.g., gs_athens_01)
org_id Integer FK -> Organization Owning organization
name String Not null Human-readable station name
lat Float Not null Latitude (decimal degrees)
lng Float Not null Longitude (decimal degrees)
alt Float Not null Altitude (meters above sea level)
api_key String Not null Station authentication key
network_id Integer SatNOGS Network station ID (for metadata sync)
created_at DateTime Default: now Provisioning timestamp

Campaign

Column Type Constraints Description
id Integer PK, auto-increment Campaign identifier
org_id Integer FK -> Organization, not null Owning organization
name String Not null Campaign display name
satnogs_id String SatNOGS satellite identifier
norad_id Integer Not null NORAD catalog number
priority Integer Default: 5 Priority level (1-10, higher = more important)
status String Default: draft Lifecycle status: draft, scheduled, active, completed, cancelled
transmitter_uuid String Selected transmitter UUID (null = auto-select)
created_at DateTime Default: now Campaign creation timestamp
updated_at DateTime Default: now Last modification timestamp

Assignment

Column Type Constraints Description
id Integer PK, auto-increment Assignment identifier
campaign_id Integer FK -> Campaign, not null Parent campaign
station_id Integer FK -> Station, not null Assigned ground station
status String Default: active Status: active, paused, completed
created_at DateTime Default: now Assignment creation timestamp

Unique constraint on (station_id, campaign_id) prevents duplicate station-campaign assignments.

Mission (Deprecated)

The Mission model is retained for backward compatibility but is replaced by Campaign. New code should use Campaign exclusively.

Column Type Constraints Description
id Integer PK, auto-increment Mission identifier
sat_id String Not null SatNOGS satellite identifier
norad_id Integer Not null NORAD catalog number
name String Not null Satellite display name
is_active Boolean Default: false Currently active mission flag (deprecated)
created_at DateTime Default: now Mission creation timestamp

SatelliteCache

Column Type Constraints Description
sat_id String PK SatNOGS satellite identifier
norad_id Integer Not null NORAD catalog number
name String Not null Satellite name
tle_json JSON Cached TLE orbital elements
updated_at DateTime Default: now Last sync from SatNOGS DB

SatelliteCache is shared across all organizations (orbital data is not org-specific).

Transmitter

Column Type Constraints Description
uuid String PK SatNOGS transmitter UUID
sat_id String FK -> SatelliteCache Parent satellite identifier
description String Transmitter description
downlink_low Integer Downlink frequency in Hz
mode String Modulation mode (e.g., FM, CW, AFSK)
alive Boolean Default: true Active status from SatNOGS

RBAC Model

Role Permissions
owner Full access: manage members, delete organization, all operator permissions
operator Manage stations, create and activate campaigns, manage assignments
viewer Read-only access to dashboard, campaigns, and station status

Security Architecture

Security hardening has been applied as part of the monorepo consolidation (Phase 1). The following describes the current and target security posture.

Authentication

  • Session cookies signed with itsdangerous (URLSafeTimedSerializer), loaded from environment-sourced SECRET_KEY
  • Magic link login with expiring tokens (10-minute TTL via PyJWT)
  • HttpOnly, Secure, SameSite=Lax cookie attributes in production
  • No hardcoded secrets in source code; all credentials via environment variables and .env file (gitignored)

MQTT Security

  • Mosquitto configured with username/password authentication (MQTT_USER / MQTT_PASS)
  • Anonymous access disabled in broker configuration
  • Target state: per-client MQTT credentials (Director, each Agent, Core API)
  • Target state: MQTT TLS on port 8883 with topic ACLs restricting Agent access to their own station prefix

Network Security

  • PostgreSQL port not exposed to host (internal Docker network only)
  • SECRET_KEY, MQTT_PASS, and database credentials stored in .env file, never committed
  • Target state: separate Docker networks for frontend (API + broker) and backend (database + director)
  • Target state: Agent validates rotator addresses against local allowlist

Authorization

  • Role-based access control (RBAC) with three roles: owner, operator, viewer
  • All API endpoints are org-scoped (/api/org/{slug}/...) and require authentication
  • Operators can manage stations and campaigns within their organization
  • Viewers have read-only dashboard access
  • Owners can manage organization membership and delete the organization
  • Station ownership is scoped to the organization, not individual users

Deployment Topology

TALOS deploys as a Docker Compose stack defined in ops/docker-compose.yml. Database schema changes are managed by Alembic; migrations run automatically on container startup or can be applied manually with alembic upgrade head.

+-------------------------------------------------------------------+
|                        Docker Host                                |
|                                                                   |
|  +------------------+    +------------------+                     |
|  |   PostgreSQL     |    |    Mosquitto     |                     |
|  |   (db)           |    |    (broker)      |                     |
|  |   Port: 5432     |    |   Ports: 1883,   |                     |
|  |   (internal)     |    |          9001    |                     |
|  +--------+---------+    +--------+---------+                     |
|           |                       |                               |
|           |    talos_net (bridge network)                         |
|           |                       |                               |
|  +--------v---------+    +--------v---------+                     |
|  |   Core API       |    |    Director      |                     |
|  |   (core)         |    |   (director)     |                     |
|  |   Port: 8000     |    |   (no ports)     |                     |
|  +------------------+    +------------------+                     |
|                                                                   |
+-------------------------------------------------------------------+
           |                       |
           | HTTP :8000            | MQTT :1883
           |                       |
    +------v------+        +-------v--------+-------+
    |   Browser   |        |  Agent 1  | Agent 2 |  ...
    | (Dashboard) |        | (Pi @ Site A)  (Pi @ Site B)
    +-------------+        +----------------+-------+

Docker Compose Services

Service Image Exposed Ports Purpose
db postgres:15-alpine 5432 (internal only) PostgreSQL database
broker eclipse-mosquitto:2 1883 (MQTT), 9001 (WebSocket) MQTT message bus
core Built from core/Dockerfile 8000 (HTTP) FastAPI web API and dashboard
director Built from core/Dockerfile None Director: multi-campaign physics engine

Environment Variables

Variable Used By Description
DATABASE_URL core, director PostgreSQL connection string
BROKER_HOST core, director MQTT broker hostname
SECRET_KEY core Session cookie signing key
MQTT_USER core, director, agent MQTT authentication username
MQTT_PASS core, director, agent MQTT authentication password
POSTGRES_USER db PostgreSQL superuser name
POSTGRES_PASSWORD db PostgreSQL superuser password
POSTGRES_DB db Database name

Scalability

Scale Status Key Bottleneck Mitigation Path
10 stations Works well Silent failures are the main risk Add logging and health checks (Phase 2)
100 stations Performance degrades Physics loop exceeds 0.5s time budget; pass prediction stalls the realtime loop Vectorize SGP4 with dSGP4, move pass prediction to background thread, cache ground track computation
1000 stations Not feasible Single-threaded Python loop, MQTT fan-out volume, memory pressure Shard Director by region, evaluate NATS for messaging backbone, batch propagation with GPU acceleration

Key Scaling Strategies

  • Vectorized propagation: Replace per-satellite Skyfield calls with dSGP4 (ESA) for batch/GPU-accelerated SGP4
  • Background pass prediction: Decouple pass prediction from the realtime 2 Hz loop to avoid blocking
  • Ground track caching: Eliminate redundant SGP4 calls for visualization (currently ~48 wasted calls per tick)
  • Regional sharding: Split the Director by geographic region so each instance handles a subset of stations
  • MQTT 5.0: Shared subscriptions for horizontal scaling of consumers

Hardware-in-the-Loop Testing

TALOS supports end-to-end testing with real ground station hardware. A Raspberry Pi provisioned with a USRP B200mini SDR and Yaesu G-5500 rotator runs as a dedicated GitLab CI runner, executing hardware tests on every push. The test-hil CI job validates the complete data path:

MQTT broker -> Agent -> rotctld/rigctld -> physical hardware -> telemetry readback

See HIL Testing for provisioning and configuration details.