Skip to content

Technical Telemetry Schema

Rylan Meilutis edited this page Mar 28, 2026 · 6 revisions

Telemetry Schema

The schema defines all DataEndpoint and DataType variants and is the source of truth for every language binding. All nodes that exchange telemetry must use the exact same schema (including ordering), or decoding will be undefined.

Location:

telemetry_config.json (source) ( override path with SEDSPRINTF_RS_SCHEMA_PATH)

Optional board-local IPC overlay:

  • Set SEDSPRINTF_RS_IPC_SCHEMA_PATH to a second JSON file.
  • Its endpoints and types are merged after the base schema during code generation.
  • Overlay endpoints are treated as link-local automatically.
  • Base-schema endpoints are treated as non-link-local automatically.
  • Overlay endpoint/type names must not collide with the base schema.
  • The GUI editor can load/edit/save the base schema and IPC overlay separately.

Generated outputs:

  • Rust enums and metadata: define_telemetry_schema! in src/config.rs (source)
  • C header: C-Headers/sedsprintf.h (source)
  • Python stubs: python-files/sedsprintf_rs/sedsprintf_rs.pyi (source)

Why schema order matters

The order of items in telemetry_config.json (source) defines the enum discriminants (with built-ins inserted as noted below). Those discriminants are sent on the wire and used in endpoint bitmaps. Reordering entries without updating every deployed system will break decode compatibility.

Built-ins:

  • TelemetryError data type is built-in and is appended after all schema types.
  • TelemetryError endpoint is built-in and is appended after all schema endpoints.
  • Do not define TelemetryError in the JSON schema.

Safe changes:

  • Appending new endpoints/types to the end of the list.
  • Updating documentation fields (doc).
  • Adding board-specific link-local IPC endpoints/types in a separate overlay file via SEDSPRINTF_RS_IPC_SCHEMA_PATH.

Risky changes:

  • Reordering or deleting endpoints/types.
  • Changing a type's element layout (data type or count).

Schema structure

Top-level keys:

  • endpoints: list of DataEndpoint definitions.
  • types: list of DataType definitions.

Endpoint fields

  • rust: Rust enum variant name.
  • name: wire/display name (typically ALL_CAPS).
  • doc: optional description.
  • link_local_only: derived by schema file. Endpoints coming from the IPC overlay are link-local-only; endpoints coming from the base schema are not.

Legacy config upgrade rules:

  • Older schemas may still contain broadcast_mode.
  • build.rs and define_telemetry_schema! accept that legacy field and normalize it during schema loading.
  • broadcast_mode = "Never" is upgraded to link_local_only = true.
  • broadcast_mode = "Default" and broadcast_mode = "Always" are accepted but ignored.
  • Unknown broadcast_mode values are ignored with a build warning.
  • New schemas should omit broadcast_mode entirely.

Remote forwarding is topology-driven:

  • IPC/link-local endpoints are restricted to link-local/software-bus sides.
  • Discovery advertisements tell routers which remote sides can currently reach which endpoints.
  • If multiple local/remote candidates exist for the same endpoint, routing fans out to the discovered matches.
  • If only one candidate exists, the packet is delivered only there.
  • If discovery has not learned any route for a non-local endpoint yet, relay mode falls back to flooding.

Link-local scope further constrains forwarding:

  • Base schema endpoint: the endpoint can use any side.
  • IPC overlay endpoint: the endpoint is restricted to link-local/software-bus sides and is excluded from discovery advertisements sent on non-link-local sides.

To keep packet semantics unambiguous, a type must not mix link-local-only endpoints with normal endpoints.

Type fields

  • rust: Rust enum variant name.
  • name: wire/display name.
  • doc: optional description.
  • reliable: optional boolean (legacy). true maps to ordered reliable delivery.
  • reliable_mode: optional string (None, Ordered, Unordered). Overrides reliable when present.
  • class: Data, Warning, or Error (used for formatting and error handling).
  • element: payload layout description (see below).
  • endpoints: list of endpoint Rust variant names (used as metadata for defaults/validation).

Element fields

  • kind: Static or Dynamic.
  • data_type: primitive element type (Float32, UInt16, String, Binary, etc.).
  • count: only for Static (number of elements).

How element layouts map to bytes

This library treats the payload as a raw byte slice. The schema tells it how to interpret that slice:

  • Static + numeric/bool: payload size must equal count * element_width.
  • Dynamic + numeric/bool: payload size must be a multiple of element_width.
  • String: dynamic UTF-8 bytes; trailing NULs are ignored for validation.
  • Binary: raw bytes (no UTF-8 requirements).
  • NoData: zero-length payload (used for marker messages).

For static String or Binary payloads, the schema uses the compile-time limits:

  • STATIC_STRING_LENGTH
  • STATIC_HEX_LENGTH

These are configured in src/config.rs (source) and used by data_type_size.

Rust-side metadata

The macro-generated metadata types are defined in src/lib.rs (source):

  • MessageMeta { name, element, endpoints, reliable }
  • MessageElement::{Static, Dynamic}
  • MessageDataType (primitive element type)
  • MessageClass (Data/Warning/Error)

Helpers:

  • message_meta(ty): returns the full MessageMeta.
  • get_data_type(ty): returns the primitive element type.
  • get_needed_message_size(ty): returns the static payload size (bytes).
  • endpoints_from_datatype(ty): returns endpoints listed in the schema.
  • is_reliable_type(ty): returns whether the type uses reliable delivery on the wire.

How the schema is used at runtime

  • Packet::new validates payload sizes against message_meta.
  • Router::log* uses the schema to validate payload lengths before serialization.
  • Packet::to_string uses MessageClass and MessageDataType to format payloads.

Example

{
  "endpoints": [
    { "rust": "Radio", "name": "RADIO", "doc": "Downlink radio" }
  ],
  "types": [
    {
      "rust": "GpsData",
      "name": "GPS_DATA",
      "doc": "GPS data",
      "reliable": false,
      "class": "Data",
      "element": { "kind": "Static", "data_type": "Float32", "count": 3 },
      "endpoints": ["Radio"]
    }
  ]
}

An IPC overlay file uses the same shape. Its endpoints become link-local automatically because they come from the overlay file, not because they carry a separate flag.

Legacy schema example accepted during upgrade:

{
  "endpoints": [
    {
      "rust": "SoftwareBus",
      "name": "SOFTWARE_BUS",
      "broadcast_mode": "Never"
    }
  ],
  "types": [
    {
      "rust": "IpcMessage",
      "name": "IPC_MESSAGE",
      "class": "Data",
      "element": { "kind": "Dynamic", "data_type": "Binary" },
      "endpoints": ["SoftwareBus"]
    }
  ]
}

That legacy endpoint is treated exactly like:

{
  "endpoints": [
    {
      "rust": "SoftwareBus",
      "name": "SOFTWARE_BUS",
      "link_local_only": true
    }
  ],
  "types": [
    {
      "rust": "IpcMessage",
      "name": "IPC_MESSAGE",
      "class": "Data",
      "element": { "kind": "Dynamic", "data_type": "Binary" },
      "endpoints": ["SoftwareBus"]
    }
  ]
}

Compatibility checklist

Before deploying a schema change:

  • Ensure every node uses the same telemetry_config.json (source) order.
  • Regenerate C and Python bindings via build.rs (source).
  • Verify that any static sizes have not changed unexpectedly.
  • Redeploy all nodes that exchange telemetry.

Clone this wiki locally