Stellar ControlMission control · by Stellar Systems v0.1.0

SDKs and Integration

Extending Stellar Control

Drivers, transports, gateways, output connectors and pass feeders talk to the MCS through a NATS contract.

The core of Stellar Control never touches a byte of a target. Everything that depends on a platform, a medium or a downstream system lives in external components: processes outside the MCS that talk to it through NATS only (or, for pass feeders, through the HTTP API). This section explains how to write them.

The components you can write#

ComponentWhat it doesTalks throughContract
DriverEncodes semantic telecommands into bytes (ACK 1), decodes telemetry into raw values, recognises echoes, extracts file chunks, decodes recorded filesNATSDriver contract
TransportFrames the units of a driver for a medium (CCSDS TC frames under COP-1, CSP over KISS…), and backNATSDriver contract, Transports
GatewayCarries frames to and from a ground station, a bench or an EGSE (ACK 2), reports link availability and throughputNATSGateway contract
Output connectorConsumes the data of the MCS to store it (a time-series database) or relay itNATS (JetStream consumers)Connector contract
Pass feederPushes the passes of a flight dynamics system or a ground station providerHTTP APIPOST /v1/passes, POST /v1/passes/replace_window
flowchart LR
    subgraph MCS["Core of the MCS"]
      EX[Executor]
      CO[Compute stage]
      RE[Reconciler]
    end
    EX -- "SemanticTc<br/>stellar.tc.encode.*" --> D[Driver]
    D -- "unit<br/>stellar.tc.wrap.*" --> T[Transport]
    T -- "frame<br/>stellar.tc.uplink.*" --> G[Gateway]
    D -. "frame (no transport)" .-> G
    G -- "raw frame<br/>stellar.tm.raw.*" --> T
    T -- "unit<br/>stellar.tm.unit.*" --> D
    D -- "RawSample[]<br/>stellar.tm.decoded.*" --> CO
    RE -- "bindings, credentials" --> D & T & G
    MCS -- "JetStream consumers" --> C[Output connector]
    F[Pass feeder] -- "HTTP" --> API[API]

The contract is the interface#

The contract is JSON over NATS, independent of any language: registration, heartbeats, control verbs, bindings and the data plane. The MCS checks it at registration and binds only the instances that fit the topology. The NATS contract reference lists every subject, header and message; stellar schema <kind> prints the JSON Schema of each message.

Two SDKs implement it, so that a component only declares what it does:

Rust SDKPython SDK
Packagecrate stellar-sdk (sdk/rust/sdk), with stellar-contract and stellar-commonstellar-mcs on PyPI (sdk/python)
DriverDriver trait, driver_main / run_driverDriver with @driver.encode, @driver.decode, @driver.echo, @driver.chunks, @driver.decode_file
TransportTransport trait, transport_main / run_transportTransport with @transport.wrap, @transport.unwrap
GatewayGateway trait, run_gatewayGateway with @gateway.send, @gateway.receive, @gateway.link_available
Output connectorConnector trait, run_connectorConnector with @connector.on("<kind>")
Pass feeder— (plain HTTP)PassFeeder
Own control verbsRpc trait@component.rpc("verb")
COP-1 in a transportyes (Transport::cop1, Transport::clcw)no
CFDP entity in a driveryes (Driver::cfdp and friends)no
Protocol helpersCCSDS space packets and frames, CLCW, COP-1 FOP and FARM, CFDP entity, CSP 1/2, KISS, CAN frames, CSP over CAN (CFP)CSP 1/2, CAN frames, CSP over CAN (CFP)
Prometheus endpointyes (STELLAR_METRICS_ADDR)no

Both SDKs handle the same chores: the nkey key pair, registration with retries, the JWT issued by the reconciler and its renewal, heartbeats, the common control verbs (status, credentials, bindings, reregister), subscriptions derived from the bindings, JetStream consumers, telecommand events (ACK 1 and ACK 2), deadlines, fragments and reconnection.

A component in another language sends the same messages: nothing in the MCS knows which SDK, if any, a component uses.

Choosing#

  • Python for a quick integration, a bench instrument, a text protocol, a connector or a feeder script. The examples in examples/python carry a SCPI power supply into the MCS in a few dozen lines each.
  • Rust for flight links: CCSDS framing, COP-1, CFDP, strict timing, test vectors replayed bit for bit, and a Prometheus endpoint.
  • Any language when a component must live in an existing code base: follow the NATS contract.

Layering#

The repository enforces one-way dependencies (scripts/check-layers.py, run in CI): sdk ← core ← tooling, and examples depend on the SDK only. A driver embeds the contract and the SDK, never the core; the core is shipped without the SDK. What an example does, a team outside the MCS can do.

Example components#

All of them are built on the SDK alone, and are the best starting points.

PathKindBinaryWhat it shows
examples/rust/drivers/lab-psudriverscpi-psuThe smallest driver: SCPI-like text lines of the lab-psu catalogue
examples/rust/drivers/platform-v3driverplatform-v3-ccsdsPUS-C packets behind the ccsds-tc transport: echoes from verification reports, file chunks, LTTM decoding, test vectors (examples/icd/platform-v3.md)
examples/rust/drivers/obc-cspdriverobc-cspWhole CSP 1 packets, the ground address read from the link parameters (csp_address), with any transport (examples/icd/obc-csp.md)
examples/rust/drivers/tcu-candrivertcu-canA partial driver covering the tcu component only, on the CAN bench bus (examples/icd/tcu-can.md)
examples/rust/drivers/satlink-benchdriversatlink-benchAn RF bench as a target, through its control plane (examples/icd/satlink-bench.md)
examples/rust/transports/ccsds-tctransportccsds-tcSpace packets in TC frames under COP-1, packets reassembled from TM frames
examples/rust/transports/csp-kisstransportcsp1-kiss, csp2-kissCSP packets in KISS frames, CRC32 added and checked
examples/rust/transports/csp-cantransportcsp1-can, csp2-canCSP packets on a CAN bus in the CFP of libcsp, checked against libcsp's own frames
examples/rust/gateways/cangatewaybench-canA SocketCAN bus; an in-memory bus for tests
examples/rust/gateways/satlinkgatewaysatlink-gatewayA bench with a REST control plane and a ZeroMQ data plane
examples/rust/gateways/benchgatewaybench-gatewayThe smallest gateway: logs frames, publishes a status frame every second
examples/rust/connectors/timescaleconnectorstellar-timescaleThe reference output connector, to TimescaleDB
examples/python/driver.pydriver—The scpi-psu driver in Python
examples/python/gateway.pygateway—A TCP gateway to an instrument: polling, link state, reconnection, a control verb
examples/python/connector.pyconnector—Measures and telecommand events to CSV files, idempotently
examples/python/csp_can.pytransport—csp1-can and csp2-can in Python
examples/python/pass_feeder.pypass feeder—An FDS planning or provider bookings pushed as snapshots

How a component joins a target#

  1. The topology declares a link of a target with a driver (by name and version, platform-v3-ccsds@4), an optional transport and a gateway (by name, or a tag constraint such as any(tag: sband)).
  2. The component starts with bootstrap credentials limited to registration and heartbeats, and registers: software name and version, and what it implements.
  3. The reconciler checks the registration, binds the instance to the links it fits, issues it a JWT with exactly the permissions of its bindings, and sends it its bindings.
  4. The component subscribes to the subjects of its bindings; the target becomes ready when every link has a driver, a transport when declared, and a gateway bound.

See Drivers, Transports and Gateways for the binding rules seen from the topology.

Stellar Control · v0.1.0

↑↓ to moveEnter to open