Stellar ControlMission control · by Stellar Systems v0.1.0

SDKs and Integration

Writing a Pass Feeder

Push passes from a flight dynamics system or a ground station provider to the MCS.

The MCS never fetches passes. Third-party scripts push them, at their own pace: a flight dynamics system (FDS) pushes its predictions, a ground station provider its bookings. The MCS exposes an injection contract on its HTTP API, not a polling frequency. This page shows how to write such a pass feeder, with plain HTTP or with the PassFeeder class of the Python SDK. See Passes for how the MCS merges and uses them.

Identity and source#

Each feeder has its own service identity and writes only the passes of its source. Feeders are declared in the global configuration of the MCS, identity → source:

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

The API takes the identity of the caller from its token (Authorization: Bearer <token>, where an identity provider is configured), or from X-Stellar-User. A caller that is not a feeder gets 403 pass::not-a-feeder; a feeder writing another source 403 pass::forbidden-source. See Identity and Roles.

The pass#

FieldTypeNotes
idtokenStable identifier, chosen by the feeder: give a pass the same one each time you refine it
targettokenA target of the current configuration (else 422 pass::unknown-target)
stationtokenGround station; a gateway carrying this tag serves the pass
aos, losRFC 3339, UTCAOS before LOS
max_elevationangle"47.3 deg", between 0 and 90°
uplinkboolean, default truefalse: a receive-only pass, which refuses any telecommand
transceiveroptionalOn-board transceiver: sband, xband…
protocoloptionalccsds, csp, dvb-s2…
ratesoptional{uplink: "64 kbps", downlink: "2 Mbps"}: expected bit rates, used by the transfer manager when no measured rate is available
sourcefds or providerThe source of the feeder
statuspredicted, booked or cancelledA fds pass cannot be booked

The MCS adds feeder, updated_at and, for a pass merged from both sources, contributions (the last version from each source).

Two modes#

One pass: upsert#

POST /v1/passes writes one pass, new or refined, and answers the pass as stored (merged with the other source's), with the gateway checked for it:

Shell
curl -X POST http://mcs:8080/v1/passes \
  -H 'X-Stellar-User: fds-feeder' -H 'Content-Type: application/json' \
  -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"}'

A complete snapshot: replace a window#

POST /v1/passes/replace_window takes every pass of a source for a target over an interval: each pass is written, and the contribution of the source to every pass of the interval absent from the snapshot is cancelled. It is the natural mode of a script that exports its whole planning.

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

The reply lists the identifiers written and cancelled: {"upserted": […], "cancelled": […]}. Every pass of the snapshot must be of the target and overlap the interval. A booking that stays in place keeps the merged pass booked.

Matching and merging#

A submitted pass is matched, in order, with the pass stored under its identifier, with the pass one of whose contributions has that identifier, then with a pass of the same target and station whose window overlaps. Otherwise it is new. The merged pass keeps the identifier under which it was first stored; it takes from the booking the window, the elevation, the status and uplink; transceiver, protocol and rates come from the booking, else from the prediction. Writes are conditional and retried on conflict.

The Python PassFeeder#

Python
from stellar_mcs import Pass, PassFeeder, PassRefused

async with PassFeeder("http://mcs:8080", "fds-feeder", source="fds") as feeder:
    try:
        replaced = await feeder.replace_window("sat1-fm", start, end, [
            Pass("gs-a-20261002T1014", "sat1-fm", "gs-a", aos, los, max_elevation=47.3),
        ])
        print(replaced.upserted, replaced.cancelled)
    except PassRefused as refused:
        print(refused.status, refused.codes)
MemberRole
PassFeeder(url, identity=None, *, source, token=None, timeout=10.0)A feeder of source ("fds" or "provider"). With token, it sends Authorization: Bearer; else X-Stellar-User: identity. An async context manager (close() otherwise).
await upsert(one)Writes one pass; returns the Pass as stored
await replace_window(target, start, end, passes)Sends a snapshot; returns Replaced(upserted, cancelled)
await passes(target=None, *, all=False)The passes the MCS holds, not over (every one with all)
Pass(id, target, station, aos, los, max_elevation, status="predicted", uplink=True, transceiver=None, protocol=None, uplink_rate=None, downlink_rate=None)A pass; elevation in degrees, rates in bit/s, times as aware datetime. source, feeder, updated_at and contributions are filled by the MCS.
PassRefusedThe MCS refused: status, errors ([{code, message, help}]) and codes

The example feeder#

examples/python/pass_feeder.py reads the planning an FDS exports as CSV, one pass per line, and sends it as one snapshot per target over the interval its passes cover:

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
gs-a-20261002T1327-sat1,sat1-fm,gs-a,2026-10-02T13:27:02Z,2026-10-02T13:37:55Z,71.0,true,

uplink (true by default) and status are optional: a pass of the FDS is predicted, one of a provider booked, unless the file says cancelled.

Shell
cd examples/python
uv run pass_feeder.py planning.csv                       # identity fds-feeder, source fds
uv run pass_feeder.py planning.csv --watch 60            # pushes again when the file changes
uv run pass_feeder.py bookings.csv --identity station-feeder --source provider
uv run pass_feeder.py --demo sat1-fm                     # invented passes of the next 24 h
stellar passes --target sat1-fm

bookings.csv holds a booking (ksat-7781) that overlaps the first prediction on the same station: the MCS merges them, and the merged pass takes the window and status of the booking. A pass removed from the next export of the planning is cancelled, since the planning is sent as a snapshot of its interval. The script takes --api (default http://127.0.0.1:8080).

The core of the script:

Python
async def push(feeder: PassFeeder, passes: list[Pass]) -> None:
    """One snapshot per target, over the interval its passes cover."""
    by_target: dict[str, list[Pass]] = defaultdict(list)
    for p in passes:
        by_target[p.target].append(p)
    for target, planned in sorted(by_target.items()):
        start = min(p.aos for p in planned)
        end = max(p.los for p in planned)
        try:
            replaced = await feeder.replace_window(target, start, end, planned)
        except PassRefused as refused:
            log.error("%s: passes refused: %s", target, refused)
            continue
        log.info("%s: %d passes written, %d cancelled", target,
                 len(replaced.upserted), len(replaced.cancelled))

From the CLI#

For a one-off import, stellar passes import <file> sends a file in the format of the specification (passes:), as the feeder named by --as; with a header replace_window: {source, target, from, to}, it sends a snapshot. See the CLI Reference.

Errors#

StatusCodeMeaning
401auth::invalid-token, auth::no-providerToken refused, or no identity provider configured
403pass::not-a-feederThe identity is not in passes.feeders
403pass::forbidden-sourceThe feeder writes another source
422pass::invalidInvalid body or pass (window, elevation, rates, a fds pass booked…)
422pass::unknown-targetNo such target in the current configuration
503api::unavailable, api::no-configurationNATS unavailable, or no configuration published

See the API reference for the full schemas.

Stellar Control · v0.1.0

↑↓ to moveEnter to open