Skip to main content

Telemetry — how it works

Telemetry turns the protobuf messages a spacecraft downlinks into named values you can read as latest readings and plot on charts. You declare the values you care about (fields) and how to pull them out of a decoded message (processors). That's the whole model.

The pieces

  • Field definition — one named, typed value you want to track (e.g. battery_voltage, a float). Workspace-scoped, optionally scoped to a subsystem.
  • Processor — how a decoded message becomes field values. A processor names a protocol version and a message type, then maps selectors (paths into the decoded message) onto field definitions.
  • Sample — one stored reading of a field over time; the time series behind charts.
  • Latest value — the most recent sample per field, kept in memory for the console and latest-value cards.

Telemetry rides on the same protobuf-over-UDP pipeline as commands — see Protocols, commands & UDP for how packets arrive and get decoded.

The 5-minute path

To capture and graph a couple of fields from one message:

  1. On the Field Dictionary page, create a field per value you want — give it a name and a value type.
  2. On the Processors page, create a processor: choose the protocol version and the message type the value lives in.
  3. Add a mapping row per field: use the field tree to pick the value, which fills in the selector (the path into the message), and point it at your field definition.
  4. Send some telemetry. Latest values and charts appear on the satellite's telemetry view.

That's all that's required. Everything below is optional and only matters as your needs grow.

Selectors — pointing at a value

A selector is a bare path into the decoded message (the field tree writes these for you). It supports:

  • Nested fieldstelemetry.load_average_1m.
  • List indextelemetry.fan_rpm[0].value.
  • Match by contenttelemetry.temperatures[name="cpu"].value finds the list element whose name equals cpu and reads its value. The match value is compared as text.

A selector that doesn't resolve (missing field, wrong list element) simply contributes nothing — it doesn't error. This is how a processor for the wrong message in an envelope stays quiet.

Two common telemetry shapes

Real spacecraft telemetry tends to be a collection of readings, each tagged with an identity. The only question is where that identity lives — and both shapes work today by listing one mapping per reading:

  • Identity in the field name (structural). Each channel is its own named field, e.g. a power monitor reporting vip_cnt_ch14.volt, vip_cnt_ch14.curr, vip_cnt_ch14.pwr. Map each path to a field (vip_cnt_ch14.pwr → ch14_power).
  • Identity in the data (content). Readings sit in a repeated list and carry an id field, e.g. SensorReading { sensor_id, value }. Use a content-match selector per id: top_readings[sensor_id="14"].value → ch14_value.

A more compact, table-driven way to handle many or changing channels at once (instead of one mapping each) is planned — for now, list the ones you care about.

Recognising the right message (envelope convention)

AutoSat decodes an incoming packet against the message types your processors name, and a processor contributes values only when its selectors resolve. This is reliable when your messages share a single envelope with a one-of payload, e.g.:

message DownlinkEnvelope {
oneof payload {
Heartbeat heartbeat = 1;
Telemetry telemetry = 2;
Eps eps = 3;
}
}

Because exactly one branch is set per packet, each processor only matches the message that was actually sent. Wrapping all telemetry in one envelope is the recommended pattern.

warning

If a single packet produces values for more than one top-level message type, that's a sign two unrelated messages look alike on the wire — a warning is logged. Prefer the envelope above so the payload branch tells AutoSat which message it is.

Subsystems — where a reading is attributed

A field and a processor can be satellite-level or scoped to a subsystem. A subsystem-scoped processor attributes its values to the installed instance of that subsystem on the satellite.

Which instance a reading belongs to is something you configure, not something the protobuf tells us — the message gives the value, you decide the channel-to-subsystem mapping. Today a processor resolves to a subsystem's single installed instance; routing across multiple identical instances (by a hardware id in the packet) is planned.