Stellar ControlMission control · by Stellar Systems v0.1.0

Core Concepts

Telecommand Lifecycle

The states of a telecommand, its three acknowledgements, preconditions, verification, timeouts, identifiers and the rules that refuse it.

Every telecommand, whether sent by hand or by a procedure, goes through the same state machine with three acknowledgements: the driver encoded it (ACK 1), the gateway sent it (ACK 2), and the telemetry shows it had the expected effect (ACK 3). Each transition is an event, archived in a replayable stream, so that the fate of every telecommand can be followed live and audited later.

This page describes the lifecycle. To send telecommands by hand, see Direct Telecommands and Values; to declare them, see Telecommands and Verification.

States#

stateDiagram-v2
    [*] --> PENDING
    PENDING --> REJECTED: requires not met, target held, link not carrying the component
    PENDING --> ENCODED: ACK 1 (driver)
    PENDING --> ENCODE_FAILED: link not bound, driver error, no ACK 1 in time
    ENCODED --> SENT: ACK 2 (gateway)
    ENCODED --> SEND_FAILED: gateway or transport error, no ACK 2 in time
    SENT --> VERIFIED: ACK 3, every verification holds
    SENT --> VERIFY_FAILED: non-conforming echo, condition without window false
    SENT --> VERIFY_TIMEOUT: window elapsed
    SENT --> COMPLETE: no verification declared
StateFinalMeaningPublished by
PENDINGnoAccepted; preconditions not evaluated yetExecutor
REJECTEDyesRefused before encoding; detail gives the reasonExecutor
ENCODEDnoACK 1: encoded by the driverDriver
ENCODE_FAILEDyesThe driver could not encode it, the link is not bound, or no ACK 1 in timeDriver or executor
SENTnoACK 2: the gateway sent it (its last frame, when framed in several)Gateway
SEND_FAILEDyesThe gateway or the transport could not send it, or no ACK 2 in timeGateway, transport or executor
VERIFIEDyesACK 3: every verification holdsExecutor
VERIFY_FAILEDyesA non-conforming echo, or a verification without window found falseExecutor
VERIFY_TIMEOUTyesA verification did not hold within its window, or no echo cameExecutor
COMPLETEyesSent, without verification declaredExecutor

A telecommand succeeds on VERIFIED or COMPLETE; every other final state is a failure.

ECHO is an event, not a state: the driver publishes it when it recognises the on-board echo of a telecommand it encoded, with conforming: true or false.

Events#

Each transition is published on stellar.tc.evt.<target>.<tc_id>, in the TC_EVENTS stream (30 days of retention):

JSON
{"tc": "01J9Z3K8T3YB6N4R5W2Q7XH0AE", "state": "VERIFY_TIMEOUT", "at": "2026-10-02T10:15:04.211Z",
 "detail": "verification on responding not met within 5s"}
FieldMeaning
tcIdentifier of the telecommand (ULID)
stateNew state, or ECHO
atTime of the transition
detailExplanation: failed precondition, error of the driver or the gateway, timeout… (optional)
conformingFor an ECHO event: whether the echo conforms
telecommandFor a PENDING event: {component, instance, telecommand}, which feeds sent_at in derived measures

Follow a telecommand over WebSocket with GET /v1/tc/{target}/{id}/events: it replays the events already published, follows the new ones, and closes on a final state. stellar send does exactly that. See WebSocket Streams.

The chain step by step#

sequenceDiagram
    participant C as API / procedure
    participant E as Executor
    participant D as Driver
    participant T as Transport (optional)
    participant G as Gateway
    participant V as Compute stage
    C->>E: telecommand (tc.submit, or in a run)
    E->>E: PENDING, then requires, lease, link
    E->>D: tc.encode.<codec>.<target>
    D-->>E: ENCODED (ACK 1)
    D->>T: tc.wrap.<transport>.<target>
    T->>G: tc.uplink.<gateway>.<target>
    G-->>E: SENT (ACK 2)
    G->>D: tm.raw (telemetry, echo)
    D-->>E: ECHO
    D->>V: tm.decoded
    V-->>E: param samples
    E->>E: VERIFIED / VERIFY_FAILED / VERIFY_TIMEOUT / COMPLETE

1. Submission and PENDING#

A direct telecommand is submitted to the API (POST /v1/tc), which resolves it against the current snapshot, gives it an identifier and publishes it on stellar.tc.submit.<target>. The executor runs it as an implicit one-step procedure. A telecommand of a procedure is created by the executor, which writes it in the log of the run before sending it. Either way, the executor publishes PENDING, which carries the run and the step for a procedure, and the identity of the operator for a direct telecommand.

2. Checks before encoding#

Before anything is sent, the executor refuses the telecommand with REJECTED when:

  • a precondition does not hold. The requires of the telecommand are evaluated on the current values, the samples published but not yet in the table included: a telecommand sent right after a wait until or the verification of the previous one sees what they saw. A condition that is false, or that reads a value absent or older than its max_age, rejects the telecommand; detail gives the reason and the values read, with their age: precondition on `pressure`, `primary_pump` does not hold: pressure = 101325 Pa (received 0.8 s ago), primary_pump = RUNNING (received 0.3 s ago).
  • the target is held by a run. See Leases below.
  • the link does not carry the component. A link restricted to some components (components: [tcu]) refuses a telecommand of another component.
  • the telecommand or the target is unknown to the configuration revision of the telecommand.

3. Encoding (ACK 1)#

The executor publishes the telecommand on the encoding subject of the binding of its link — the link of the telecommand (via in a procedure, link for a direct telecommand), else the default link of the target. A link not bound gives ENCODE_FAILED.

The telecommand is semantic: a component, an instance, a telecommand name and named arguments, with defaults applied, numbers in the unit of the argument, enum values as strings, bytes as hexadecimal. The driver encodes it and publishes ENCODED, or ENCODE_FAILED with the reason.

4. Sending (ACK 2)#

The driver hands the encoded unit to the transport of the link, if any, which frames it, or directly to the gateway. The gateway sends it and publishes SENT, or SEND_FAILED.

  • A unit framed into several frames carries Stellar-Fragment: <i>/<n> on each; the gateway publishes SENT after the last one, SEND_FAILED at the first failure.
  • A unit the transport cannot frame (for instance while COP-1 is locked out) fails the telecommand with SEND_FAILED and the reason: the driver already gave ACK 1.

5. Verification (ACK 3)#

ACK 3 is a predicate on measures, not a correlation of packets. The verify list of the catalogue combines an echo and conditions on the measures of the component instance:

YAML
telecommands:
  set_mode:
    args:
      mode: {type: tcu_operating_mode}
    verify: [echo, {mode: args.mode, within: 10s}]
  • Only samples received after SENT count. A condition is evaluated only on samples of its measures whose ground reception time follows SENT, which avoids a false success on an older value.
  • With within, the condition must become true within the window, which starts at SENT. Otherwise the telecommand ends VERIFY_TIMEOUT.
  • Without within, the condition is judged on the first samples received after SENT: false gives VERIFY_FAILED.
  • echo is checked by the driver, the only one that knows the bytes: it compares each frame received with the telecommands it encoded recently and publishes an ECHO event. A non-conforming echo gives VERIFY_FAILED; no echo in time gives VERIFY_TIMEOUT.
  • Every verification must hold for VERIFIED.
  • Conditions may reference the arguments (args.mode).

verify and within are optional. Without them, the telecommand goes from SENT to COMPLETE, without ACK 3. This is the case of a deferred telecommand executed later by the spacecraft: its effect is checked afterwards by an explicit check in a procedure, for instance at the next pass.

Timeouts and deadlines#

executor.ack_timeout (10 s by default) bounds each wait of the chain:

WaitLimitState on expiry
ACK 1ack_timeout after PENDING (plus one second of grace)ENCODE_FAILED (no ACK 1 within …)
ACK 2ack_timeout after the deadline of ACK 1SEND_FAILED (no ACK 2 within …)
Echo, or first samples of a verification without withinack_timeout after SENTVERIFY_TIMEOUT
Verification with withinwithin after SENTVERIFY_TIMEOUT

The telecommand and its frames carry a deadline, the Stellar-Deadline header (ack_timeout after PENDING). A driver or a gateway that receives them too late refuses them (ENCODE_FAILED, SEND_FAILED) instead of sending a stale command: a telecommand not uplinked in time is never sent later by surprise.

Identifiers and deduplication#

Each telecommand gets a ULID when it is created: by the API for a direct telecommand (or by the client, which may give its own id to make its submission idempotent), by the executor for a telecommand of a procedure. The identifier is the Nats-Msg-Id of each stage in the TC_COMMANDS stream, so that a network duplicate or a resend after a restart of the executor is deduplicated by JetStream:

StageNats-Msg-Id
Submission (stellar.tc.submit.…)<tc_id>
Telecommand to encode<tc_id>-encode
Frame to uplink<tc_id>-uplink
Event<tc_id>-<STATE>

A direct submission answers {"id": "…", "duplicate": true} when the same identifier was already submitted: nothing new is sent. The TC_COMMANDS stream deduplicates over 10 minutes.

A retry is a new attempt, with a new identifier, attached to the telecommand of the first attempt (retry_of in the run log). Automatic retries are only allowed for telecommands declared changes_state: false; any other needs an operator decision. See Retries.

Order#

The telecommands of a target are encoded and sent in the order they were submitted, even when the driver or gateway of the link moves to another instance: each encoding and uplink subject is consumed one message at a time. See Architecture.

Leases and direct telecommands#

A run holds a lease on each of its targets (see Leases and Crash Recovery). While a target is held:

  • a direct telecommand is refused: 409 tc::target-held from the API, REJECTED from the executor;
  • except a telecommand declared changes_state: false while the lease is shared (a run that only checks and sends telecommands without effect).

The telecommands of a file transfer started by a run carry the Stellar-For-Run header and pass the lease of that run.

Environment of a telecommand#

A telecommand carries the environment of its run. The environment selects the parameters of the link, which can differ by environment (a CSP address in AIT and another in IVV).

  • A direct telecommand may declare it (environment in POST /v1/tc, stellar send --environment, a field of the web form). It must when its link overrides its parameters by environment and its target is engaged in several (tc::environment-required); a target engaged in a single environment gives it to its direct telecommands.
  • A telecommand without environment, such as those of the transfer manager, takes the environment of the last telecommand of its link.
  • On reception, the context of the link is also that of its last telecommand, else the base parameters; decoded values carry the Stellar-Environment header.

Resolution errors of direct telecommands#

POST /v1/tc resolves the request against the current snapshot. An unknown target answers 404; every other problem answers 422 with the list of errors {code, message, help}:

CodeMeaning
tc::unknown-targetNo such target (404)
tc::missing-catalogueThe catalogue of the target is not in the snapshot
tc::unknown-linkNo such link on the target
tc::unknown-environmentThe target is not engaged in that environment
tc::environment-requiredThe link has parameters by environment: name the environment
tc::unknown-telecommandThe platform has no such telecommand
tc::ambiguous-telecommandSeveral components have it: write component.name
tc::link-componentThe link does not carry the component
tc::missing-instance, tc::unexpected-instance, tc::unknown-instanceInstance of a multi-instance component missing, superfluous or unknown
tc::missing-argument, tc::unexpected-argumentArgument without default missing, or unknown
tc::invalid-argumentValue of the wrong type or unit
tc::out-of-rangeValue outside the range of the argument
tc::target-heldThe target is held by a run (409)

See Error Codes for the codes of the whole API.

Stellar Control · v0.1.0

↑↓ to moveEnter to open