Skip to content

Core Program Architecture

W9KSB edited this page Jun 10, 2026 · 1 revision

Core Program Architecture

Overview

Pi-Sat uses a simple split:

  • frontend/: browser UI
  • pi_sat_controller/backend/: API, tracking logic, device control, orbital calculations, and data refresh

The browser never talks directly to radios, SDRs, or rotators. All hardware interaction is centralized in the backend.

Why It Is Structured This Way

The project is built this way for a few reasons:

  • hardware access belongs on the Pi, not in the browser
  • tracking logic should exist in one authoritative place
  • the frontend should stay stateless enough to recover cleanly after refreshes
  • radio, SDR, and rotator control need shared coordination rules rather than scattered UI logic

This keeps the control path predictable and easier to troubleshoot.

Frontend Structure

The frontend is intentionally small:

  • frontend/index.html
    • page shell
    • major dashboard/page layout
    • footer and global containers
  • frontend/style.css
    • application styling
    • layout, sizing, tables, cards, controls, and map styling
  • frontend/app.js
    • API polling
    • state management for selected satellite/profile/pass
    • rendering for dashboard, satellites page, map page, monitor page, and settings
    • autotrack UI interactions
    • map and pass arc drawing

The frontend is responsible for presentation and operator actions. It does not own orbital truth or device state.

Backend Structure

Main Application Layer

  • backend/app.py
    • FastAPI app
    • startup/shutdown
    • scheduler threads
    • shared runtime caches
    • API endpoints
    • orchestration between the other modules

Configuration Layer

  • backend/config.py
    • loads and saves the main config file
    • loads tracked satellites
    • preserves the settings page schema
    • handles multi-URL TLE source storage

Orbital Layer

  • backend/orbital/skyfield_engine.py
    • satellite position lookup
    • ground track generation
    • pass prediction
  • backend/orbital/doppler.py
    • raw Doppler math

This layer turns TLE data into positions, velocities, pass windows, and tracking geometry.

Tracking Layer

  • backend/controller/rx_tracking.py
    • live RX/TX tracking loop
    • user offset preservation
    • Doppler application
    • rotator coordination during active passes
  • backend/controller/frequency_planner.py
    • uplink/downlink offset mapping rules
  • backend/controller/safety.py
    • TX inhibit checks

This is the behavioral core of the app.

Radio / SDR Layer

  • backend/radio/
    • Hamlib radio control wrappers
    • local rigctld management
    • network rigctld client support
  • backend/sdr/
    • polled SDR readback and write path

This layer isolates device communication from tracking logic.

Rotator Layer

  • backend/rotator/
    • network rotctld support
    • local rotctld support
    • rotator model loading
    • pass-active/manual-control policy

The rotator path is intentionally separate from radio/SDR control because the command rules are different. The goal was to break these things out to allow them to be independent.

Satellite Data Layer

  • backend/satellites/tle_manager.py
    • TLE download, merge, cache, and parse
  • backend/satellites/transponder_source_client.py
    • pulls transponder data
  • backend/satellites/satellite_profiles.py
    • reads/writes satellite profile data

Data Flow

Pass Prediction Flow

  1. TLE data is loaded or refreshed
  2. The Skyfield engine builds orbital objects from the merged TLE cache
  3. Tracked satellites are loaded from config
  4. Passes are generated and cached
  5. The frontend reads the cached pass list through the API

Live Tracking Flow

  1. The selected satellite/profile is established
  2. The backend tracking loop reads current satellite position
  3. Doppler is calculated from range rate
  4. RX target is built from center frequency plus Doppler plus user offset
  5. TX target is built if the profile is not RX-only
  6. SDR and radios are updated through the backend managers
  7. Rotator targets are sent only when pass logic allows it
  8. The frontend polls and renders the current state

Key Design Decisions

Backend-Authoritative Tracking

The browser does not calculate Doppler or pass state. The backend does. This avoids divergent logic between UI and hardware control.

Stable Manual Offset Behavior

User offset is preserved as an explicit operator input. Doppler changes move around that offset instead of resetting it every update.

TLE Merge by Newest Epoch

When multiple TLE sources contain the same NORAD ID, Pi-Sat keeps the newest entry and ignores older duplicates. This avoids an issue where one TLE data set is pulled after another, contains older data, but someone gets prioritized at the front of the line.

Local and Network Hamlib Paths

Pi-Sat supports both:

  • direct network access to existing rigctld / rotctld
  • managed local Hamlib daemon startup for directly attached serial devices

That keeps the UI and tracking logic the same regardless of how hardware is attached.

Clone this wiki locally