Stellar ControlMission control · by Stellar Systems v0.1.0

Operations

Passes

Passes from the flight dynamics system and the ground station provider: model, feeders, merging and gateway checks.

A pass is a window of visibility of a target over a ground station, between its acquisition of signal (AOS) and its loss of signal (LOS). Passes come from two sources: the flight dynamics system (FDS), which predicts them, and the ground station provider, which books them. They are not part of the configuration: they are operational state that changes often, stored in the stellar_passes KV bucket under <target>.<id>.

The MCS never fetches passes. Third-party scripts — feeders — push them through the API, at their own pace, and the MCS merges the two sources into one pass. Passes then carry scheduled runs, bound the runs launched during them, give the expected rates to the file transfers, and are checked against the gateways before their AOS.

The pass model#

YAML
passes:
  - id: gs-a-2026-10-02T1014Z-sat1
    target: sat1-fm
    station: gs-a
    aos: 2026-10-02T10:14:32Z
    los: 2026-10-02T10:24:05Z
    max_elevation: 47.3 deg
    uplink: true
    transceiver: sband
    protocol: ccsds
    rates: {uplink: 64 kbps, downlink: 2 Mbps}
    source: provider
    status: booked
FieldRequiredMeaning
idyesStable identifier, given by the feeder
targetyesTarget, as named in the topology
stationyesGround station
aosyesAcquisition of signal, UTC (RFC 3339)
losyesLoss of signal, UTC
max_elevationyesMaximum elevation, an angle (47.3 deg)
sourceyesfds (predictions) or provider (bookings)
statusyespredicted, booked or cancelled
uplinknoWhether telecommands can be sent; true by default
transceivernoOn-board transceiver: sband, xband…
protocolnoProtocol: ccsds, csp, dvb-s2…
ratesnoExpected rates, {uplink, downlink}, as bit rates (64 kbps, 2 Mbps)

The MCS adds three fields, which a feeder does not write:

FieldMeaning
feederIdentity of the feeder that wrote the last contribution
updated_atTime of the last contribution
contributionsFor a merged pass: the pass as each source last wrote it

Validation#

A pass is refused with 422 pass::invalid when:

  • id, target or station is not a token (letters, digits, - and _);
  • aos is not before los;
  • max_elevation is not an angle between 0 and 90 degrees;
  • a rate is not a positive bit rate;
  • an fds pass is booked: only the provider books a pass.

A pass whose target is not in the current configuration is refused with 422 pass::unknown-target. The environment of a pass is not written: it follows from its target.

Status#

StatusMeaning
predictedPredicted by the flight dynamics system
bookedBooking confirmed by the ground station provider
cancelledCancelled by its source

In in_orbit, only a booked pass carries a run: a run bound to a pass that is not booked is refused (409 run::not-booked), and a schedule on it is at_risk.

Receive-only passes#

A pass with uplink: false allows no telecommand. A run that sends telecommands is refused on it (409 run::receive-only), and a schedule of such a run is at_risk.

Feeders#

Each feeder script has its own identity and writes the passes of one source only. The feeders are declared in the global configuration:

YAML
passes:
  feeders: {fds-feeder: fds, station-feeder: provider}

The identity of a feeder is the subject of its service token (Authorization: Bearer), or, where no identity provider is used, its declared identity (X-Stellar-User, --as in the CLI).

RefusalCode
The caller is not in passes.feeders403 pass::not-a-feeder
The pass, or the snapshot, is of another source than the feeder's403 pass::forbidden-source
A token is refused401 auth::invalid-token

Writing a pass: upsert#

POST /v1/passes writes one pass, new or refined, and answers the pass as stored (merged with the other source when there is one). The upsert is idempotent by identifier: a refined prediction replaces the previous one.

Shell
curl -s -X POST http://localhost:8080/v1/passes \
  -H 'Content-Type: application/json' -H 'X-Stellar-User: fds-feeder' \
  -d '{"id": "gs-a-20261002T1014-sat1", "target": "sat1-fm", "station": "gs-a",
       "aos": "2026-10-02T10:14:32Z", "los": "2026-10-02T10:24:05Z",
       "max_elevation": "47.3 deg", "source": "fds", "status": "predicted"}'

Writing a snapshot: replace_window#

A script that exports its whole planning sends it as a snapshot: every pass of its source for one target over an interval. POST /v1/passes/replace_window writes each pass of the snapshot, then cancels the contribution of the source to every pass of the interval that is absent from it. A pass removed from the planning is thus cancelled.

JSON
{
  "source": "fds",
  "target": "sat1-fm",
  "from": "2026-10-02T00:00:00Z",
  "to": "2026-10-03T00:00:00Z",
  "passes": [ … ]
}

The reply lists what was written and what was cancelled, as stored:

JSON
{"upserted": ["gs-a-20261002T1014-sat1"], "cancelled": ["gs-b-20261002T1151-sat1"]}

Every pass of the snapshot must be a pass of the target that overlaps the interval, and from must precede to; otherwise the whole request is refused (422 pass::invalid). A pass still booked by the provider stays booked when the FDS drops its prediction: only the contribution of the source is cancelled.

Merging the two sources#

A prediction of the FDS and a booking of the provider for the same pass are merged into one pass. A submitted pass is matched, in this order, with:

  1. the stored pass with the same identifier;
  2. the stored pass one of whose contributions has this identifier, from the same source;
  3. a stored pass of the same target and the same station whose window overlaps it.

Without a match, it is a new pass. A pass keeps the identifier under which it was first stored; schedules and run requests may name either that identifier or the identifier of any of its contributions.

The merged pass takes from the booking (the provider contribution) its window (aos, los), its elevation, its status and uplink. The transceiver, the protocol and the rates come from the booking, else from the prediction. feeder and updated_at are those of the latest contribution. contributions keeps the latest version written by each source.

flowchart LR
    F["FDS feeder<br/>predicted, aos 10:14:32"] --> M{"same target,<br/>station, overlapping"}
    P["Provider feeder<br/>booked, aos 10:14:40"] --> M
    M --> S["Stored pass<br/>window and status of the booking<br/>contributions: fds + provider"]

Writes are conditional on the revision of the stored pass: two feeders writing at the same time never overwrite each other, the loser merges again.

Gateway of a pass#

During a pass, the gateway of the default link of the target is the one attached to the station. A gateway serves a station when it carries the name of the station as a tag, in addition to the tags the link requires: for gateway: "any(tag: sband)", a pass over gs-a needs a healthy gateway registered with the tags sband and gs-a. A link to a named gateway (a bench) ignores the station.

At every reconciliation, the leader of the reconciler checks every pass that is not cancelled and starts within 10 minutes or is in progress. The result goes to the stellar_pass_gateways bucket under <target>.<pass>, rewritten only when it changes:

JSON
{"gateway": "gs-a-sband-1", "checked_at": "2026-10-02T10:05:00Z"}
{"reason": "no healthy gateway registered with the tags `sband` and `gs-a`", "checked_at": "…"}

A missing gateway is also logged as a warning by the reconciler. The API and the CLI show the check next to each pass.

Listing passes#

Shell
stellar passes                        # passes not over yet
stellar passes --target sat1-fm
stellar passes --all                  # passes over too

One line per pass: target, identifier, station, window, elevation, status (with receive-only), the checked gateway or why there is none, the feeder and the time of the last update.

text
sat1-fm  gs-a-20261002T1014-sat1  gs-a  2026-10-02T10:14:40Z → 2026-10-02T10:23:50Z  46.9 deg  booked  gateway gs-a-sband-1  by station-feeder at 2026-10-01T18:02:11Z

The API gives the same: GET /v1/passes (?target=, ?all=true) and GET /v1/passes/{target}/{id}, each pass with its gateway check. A pass is over once its LOS is past.

Importing passes from a file#

stellar passes import <file> writes passes as the feeder named by --as (or identified by --token); the options of stellar passes come before import. The file holds a list of passes in the model above:

YAML
passes:
  - id: gs-a-20261002T1014-sat1
    target: sat1-fm
    station: gs-a
    aos: 2026-10-02T10:14:32Z
    los: 2026-10-02T10:24:05Z
    max_elevation: 47.3 deg
    source: fds
    status: predicted

Each pass is upserted in turn, and printed as stored. With a replace_window header, the file is a snapshot, sent as one replace_window request:

YAML
replace_window:
  source: fds
  target: sat1-fm
  from: 2026-10-02T00:00:00Z
  to: 2026-10-03T00:00:00Z
passes:
  - id: gs-a-20261002T1014-sat1
    target: sat1-fm
    station: gs-a
    aos: 2026-10-02T10:14:32Z
    los: 2026-10-02T10:24:05Z
    max_elevation: 47.3 deg
    source: fds
    status: predicted
Shell
stellar passes --as fds-feeder import planning.yaml
# 1 written, 1 cancelled: gs-b-20261002T1151-sat1

A feeder in Python#

The Python SDK offers PassFeeder. The example examples/python/pass_feeder.py reads a CSV planning and sends it as a snapshot per target:

Shell
cd examples/python
uv run pass_feeder.py planning.csv                                  # identity fds-feeder, source fds
uv run pass_feeder.py bookings.csv --identity station-feeder --source provider
uv run pass_feeder.py planning.csv --watch 60                       # pushes again when the file changes
uv run pass_feeder.py --demo sat1-fm                                # invented passes of the next 24 h
stellar passes --target sat1-fm
text
id,target,station,aos,los,max_elevation,uplink,status
gs-a-20261002T1014-sat1,sat1-fm,gs-a,2026-10-02T10:14:32Z,2026-10-02T10:24:05Z,47.3,true,
gs-b-20261002T1151-sat1,sat1-fm,gs-b,2026-10-02T11:51:10Z,2026-10-02T11:58:40Z,18.6,false,cancelled

The booking ksat-7781 of bookings.csv overlaps the first prediction on the same station: the MCS merges them, and the merged pass takes the window and status of the booking. See Writing a Pass Feeder.

When a pass changes#

Every change of a pass is seen by the scheduler, which evaluates again the schedules bound to it: a schedule whose run no longer fits becomes at_risk, a schedule on a cancelled pass is cancelled, and the validation of a supervisor falls when the window of the pass changes. See Scheduling.

See also#

Stellar Control · v0.1.0

↑↓ to moveEnter to open