Skip to content

Telecommand uplink

This page covers how an operator’s command request becomes an uplinked PUS-C TC space packet, and how its execution gets tracked afterward.

Building the packet

command.py::TelecommandHandler owns the build-then-store pipeline (submit). The wire format itself is mission-specific, and there’s one implementation of it: missions/xtce.py::XTCETelecommandHandler.build_sp.

  • It looks up the Telecommand by name, resolves each argument’s raw value (accepting either the raw integer or, for enumerations, the label), and bitstruct.packs the fixed opcode bytes plus the arguments into the payload.
  • The packet header comes from the XTCE, not from code. A command’s container resolves to the CCSDS primary header plus a PUS-C TC secondary header (TC_HEADER_LAYOUT), followed by the payload. derive_tc_header validates that shape against every command’s container as the model is built (XTCEMission._resolve_tc_headers). That means an XTCE file that doesn’t describe PUS-C telecommands fails at startup, not on the first uplink attempt.
  • Some header fields the operator must supply per command — currently just the target APID, when a command doesn’t fix it via ArgumentAssignment. These are the one case importers.py leaves marked HEADER_ARG_UNRESOLVED; the mission layer resolves each one to a real field and a UI label (HEADER_ARG_DESCRIPTIONS).
  • minspp fills in everything computed on the ground: sequence count, packet length, and the CRC-16 PEC (SpacePacket.as_bytes(packet_error_control=True)).

The built packet is stored as a PacketTC with sent=False. The API never opens a socket — uplink is entirely the job of the ground-link bridge.

On-board execution time (PUS 11,4)

A PacketTCSubmit may carry an execution_time. When it does, submit still finishes the command normally first — its own sequence count, length, and CRC, i.e. exactly the bytes it would otherwise transmit. Then it wraps that finished command in the model’s schedule-insert command (build_schedule_insert), which is what actually gets uplinked immediately. That makes the spacecraft queue the embedded command and run it on board at the requested instant.

_check_schedulable refuses locally what the spacecraft would refuse anyway: service 11 inside a schedule, a high-priority (8,2) command, a packet over SCHED_MAX_TC_OCTETS, or a time that’s already past.

The feature is gated on Mission.scheduling_enabled, which requires both MINMCS_TC_SCHEDULING to be set and the model to define a schedule-insert command. It’s surfaced to the UI on /status.

Verification

TC verification is based on PUS service 1. XTCETelemetryPacketHandler.VERIF_CONTAINERS maps report containers to (stage, status) pairs. on_processed decodes the echoed sequence count from each report and forwards it to stores.py::PacketStore.tc_apply_verification, which closes that stage on the matching PacketTC.

Verification of a scheduled command is correlated differently, because two separate packets are involved: the schedule-insert command the MCS actually uplinked, and the embedded command it carried. PacketTC.request_id holds the first four octets of the command’s own packet — its two CCSDS primary-header words, i.e. the PUS request id. tc_apply_verification tries matching on request_id first, and falls back to the uplinked sequence count. In practice that means:

  • Reports arriving for the uplink itself are found by sequence count, and close a single queued stage — they verify the insert, not the embedded command.
  • The start/completion pair emitted at the release instant quotes the embedded command’s own id — a packet the MCS never uplinked in its own right — and closes the start/complete stages.
Last updated on