Equipment contracts: ACU and modem
Two device families report ground-segment RF: an antenna control unit (ACU) and a modem. Neither has a vendor-specific driver. An ACU or modem is any commercial controller that exposes the JSON documented here over HTTP or MQTT; a small adapter you write reads it and republishes it as telemetry.
This page is the field-by-field contract for those payloads — antenna.v1 and modem.v1 — and the line-protocol records a poller publishes from them.
Transport
ACU (antenna.v1) | Modem (modem.v1) | |
|---|---|---|
| HTTP | GET /state | GET /state |
| MQTT | constellation/antenna/<entity_id>/state (retained) | constellation/modem/<entity_id>/state (retained) |
| Encoding | UTF-8 JSON, flat object | UTF-8 JSON, flat object |
| Cadence | 1–10 Hz | 1–10 Hz |
Publish state as a retained MQTT message so a poller that restarts recovers current pointing and RF state without waiting a full interval. Every payload carries a schema field; treat an unrecognized major version as a device you cannot parse.
The antenna control unit (antenna.v1)
{
"schema": "antenna.v1",
"entity_id": "antenna-ka-1",
"ts": "2026-07-20T18:22:31.412Z",
"az_deg": 128.44,
"el_deg": 31.07,
"state": "tracking",
"link_id": "link-boulder-44713",
"temperature_c": 41.2,
"rssi_dbm": -71,
"fault": null,
"seq": 88213
}
| Field | Type | Notes |
|---|---|---|
entity_id | string | Stable device identity. Publish unchanged as the record's entity_id tag. |
ts | RFC3339 | Device event time, UTC. Distinct from ingestion time. |
az_deg / el_deg | float | Measured pointing. Azimuth [0,360), elevation [-5,90]. |
state | enum | idle | slewing | tracking | stowed | fault. |
link_id | string | null | The link this antenna is tracking. Correlates the ACU to its modem; it does not name the far end. |
temperature_c | float | Degrees C. |
rssi_dbm | int | Wi-Fi RSSI for the ACU's own management link — not RF payload signal. See Two fields named rssi_dbm. |
fault | string | null | Non-null freezes pointing at its last measured values. |
seq | int | Monotonic; a gap indicates missing sequence values; account for resets and wraparound. |
On fault, az_deg/el_deg hold their last values and state reads fault — the device does not report smooth motion it is not performing. ts is the device's own event time; do not overwrite it with poller arrival time.
The modem (modem.v1)
{
"schema": "modem.v1",
"entity_id": "modem-ka-1",
"ts": "2026-07-20T18:22:31.412Z",
"link_id": "link-boulder-44713",
"snr_db": 15.9,
"rssi_dbm": -70.4,
"locked": true,
"modcod": "32APSK_8/9",
"goodput_mbps": 84.1,
"doppler_hz": -3421.0,
"pointing_error_deg": 0.08,
"carrier_freq_ghz": 12.0,
"fault": null,
"seq": 44120
}
| Field | Type | Notes |
|---|---|---|
entity_id | string | Stable device identity. Publish unchanged as the record's entity_id tag. |
ts | RFC3339 | Device event time, distinct from ingestion time. |
link_id | string | The RF link this modem serves. Shared with the ACU's link_id so a consumer fuses geometry and RF into one link. |
snr_db | float | Estimated Es/N0. Falls with antenna pointing loss and rain. |
rssi_dbm | float | Received RF signal strength on the payload carrier. Distinct from the ACU's rssi_dbm. |
locked | bool | Carrier lock. |
modcod | string | null | Active DVB-S2X MODCOD. Null at outage. |
goodput_mbps | float | Throughput after framing overhead; 0 at outage. |
doppler_hz | float | Doppler frequency shift in Hz. See range inputs. |
pointing_error_deg | float | null | Angular error against the ACU's measured pointing. Null when the ACU is unreachable. |
carrier_freq_ghz | float | Carrier frequency. Published as the telemetry tag frequency_ghz. |
fault | string | null | Non-null forces a fault state. |
seq | int | Monotonic; check gaps, resets, and wraparound. |
At loss of lock, modcod and goodput_mbps go null/0 rather than holding a stale value.
From device JSON to telemetry
A poller reads each device payload and publishes it as line-protocol telemetry through the local socket. A few field names change on the way through:
| Device field | Telemetry field | Why |
|---|---|---|
az_deg (ACU) | azimuth_deg | Telemetry vocabulary. |
el_deg (ACU) | elevation_deg | Telemetry vocabulary. |
carrier_freq_ghz (modem) | frequency_ghz | Telemetry vocabulary; also the link's band tag. |
| everything else | same name | Published as-is. |
link_id comes from the device payload; add station_id from your site configuration. Publish both as telemetry tags.
Publish antenna and modem records as equipment with their entity_type and parent station_id tags. Publish a separate link record with entity_id, link_id, source, and target tags to describe the connection.
SNR forecasting reads features from that link record. Copy the measured SNR and pointing fields onto it, and obtain slant_range_km from a pass-geometry source. See prediction inputs.
Two field-level traps
Two fields named rssi_dbm
Both payloads have an rssi_dbm field measuring different things. The ACU's is Wi-Fi signal strength on its own management link — a health signal, unrelated to the RF payload. The modem's is received RF signal strength on the payload carrier. Publish the modem's rssi_dbm as the link's RF signal strength; publishing the ACU's under that name mislabels a Wi-Fi reading as an RF one.
Modem doppler_hz is not slant range
A modem publishes a Doppler frequency shift in doppler_hz. SNR forecasting needs distance in slant_range_km on the link entity. Obtain that distance from a device or station-side geometry calculation; a frequency shift alone does not supply it. Missing model inputs can prevent a forecast. Inspect entity_errors alongside prediction items.
Related
- Data model — the entity table and how a fused link becomes an SNR forecast.
- Send one metric — the end-to-end path this field mapping slots into.
- Protocol reference — the line-protocol format a poller writes into.
Next: Satellite contract.