Skip to content

Telemetry ingest

This page covers how a raw TM space packet becomes stored, queryable parameter values.

Pipeline

telemetry.py::TelemetryPacketHandler owns the shared pipeline (process). It leaves the wire-format specifics — parsing, container identification, bit-level decode — to a mission subclass, via hooks that raise NotImplementedError in the base class. There’s one implementation of those hooks: missions/xtce.py::XTCETelemetryPacketHandler.

  1. Parse. submit turns the submitted hex into a minspp.SpacePacket, using a PUSTMHeader configured for this mission’s wire layout: a 2-byte destination id and a 6-octet CUC counted from the 1958 epoch (the minscs config in missions/xtce.py).
  2. Identify the container. identify_container walks every concrete container’s restriction comparisons against the raw bytes. The most specific match — the one with the most comparisons — wins. This is how one physical packet type resolves to, say, a normal HK report versus a TC verification report that shares the same APID.
  3. Store the raw packet. PacketStore.tm_store (see Persistence) writes a PacketTM row. It takes on-board time from the CUC header when that’s present and plausible, and falls back to arrival time otherwise.
  4. Decode. process_telemetry resolves the container’s full entry list — inherited entries plus its own — builds one bitstruct format string across the whole packet, and unpacks it in a single pass. Each parameter’s raw value then becomes an engineering string, via whichever of its Calibrator, enumerations, or absolute-time epoch/scale applies.
  5. Store each parameter. Every decoded value becomes a ParamTM row, via TelemetryStore.store.
  6. React. on_processed is a hook for side effects after decode. The live mission uses it to detect PUS service-1 verification reports and forward them to PacketStore.tc_apply_verification — see Telecommand uplink.

The two stores

TelemetryStore writes each ParamTM to both:

  • the relational DB (SQLite/Postgres), via the same SQLModel session, and
  • InfluxDB, if MINMCS_INFLUXDB_URL/_TOKEN/_ORG are all set — see Configuration.

Those two writes are more tightly coupled than that list suggests. If InfluxDB isn’t configured, store() returns early as a no-op — and that early return happens before the relational write too, because both writes sit behind the same guard. So telemetry parameter storage is effectively all-or-nothing on whether InfluxDB is reachable, even though only the time-series half strictly needs it.

TelemetryStore.read_last() fetches the latest row per parameter name with a single grouped subquery — a join on max timestamp — rather than loading the whole ParamTM table into memory. That matters once TM has been streaming for a while and the table has grown large.

TelemetryHandler.get_subsystems() merges each System’s parameter model definitions with their latest values, grouped by APID. This is what backs /tm/subsystems and the UI’s subsystem view.

Last updated on