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.
- Parse.
submitturns the submitted hex into aminspp.SpacePacket, using aPUSTMHeaderconfigured for this mission’s wire layout: a 2-byte destination id and a 6-octet CUC counted from the 1958 epoch (theminscsconfig inmissions/xtce.py). - Identify the container.
identify_containerwalks every concrete container’srestrictioncomparisons 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. - Store the raw packet.
PacketStore.tm_store(see Persistence) writes aPacketTMrow. It takes on-board time from the CUC header when that’s present and plausible, and falls back to arrival time otherwise. - Decode.
process_telemetryresolves the container’s full entry list — inherited entries plus its own — builds onebitstructformat 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 itsCalibrator,enumerations, or absolute-time epoch/scale applies. - Store each parameter. Every decoded value becomes a
ParamTMrow, viaTelemetryStore.store. - React.
on_processedis a hook for side effects after decode. The live mission uses it to detect PUS service-1 verification reports and forward them toPacketStore.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/_ORGare 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.