APIs and Data Contracts
This section does not lock the exact schema, but it defines the contract families the implementation must support. Detailed endpoint examples and payload structures are provided to guide implementation.
1. Antenna-Node Control Endpoints
At minimum the node should expose control families equivalent to:
POST /radio/configPOST /beam/setPOST /beam/schedulePOST /tracking/startPOST /tracking/stopPOST /frequency-hold/startPOST /frequency-hold/stopPOST /frame/configPOST /stream/startPOST /stream/stopGET /metricsGET /statusGET /capabilitiesGET /frequency-hold/statusGET /scan/heatmap
Coordinate and abstract pointing controls should be explicit:
POST /pointing/az-elPOST /pointing/target-coordinatePOST /pointing/holdPOST /pointing/stop
Timestamp conventions
Timestamps are critical for cross-node correlation and post-mission analysis. Recommend the following canonical fields and formats:
timestamp_node_us: integer microseconds since Unix epoch (UTC) produced by the node's clock (GNSS-sourced when available).timestamp_central_us: integer microseconds since Unix epoch (UTC) recorded by the central aggregator on ingest.
If only a single timestamp_us field is present (legacy examples below), document whether it refers to node time or central ingest time. Always include timezone/epoch (UTC) and prefer explicit field names in new APIs.
When emitting telemetry events, include both timestamps where possible to aid reconciliation and debugging.
Machine-readable schema: telemetry-event.schema.json
Candidate source streams should stay raw or lightly annotated until the central server selects a preferred source. A node does not know which stream the central server will prefer; it simply publishes the telemetry formats it has been configured to emit. Decoding can happen centrally for the preferred stream, or at the node when a deployment explicitly enables edge pre-decode.
Node telemetry formats at a glance
| Node output | Typical format | Notes |
|---|---|---|
| Control and status API (8080) | JSON over HTTP | configuration, state, capabilities |
| Raw IQ stream (9001) | Binary complex samples | pre-demodulation I/Q bytes |
| Telemetry frames (9002) | Raw binary frame bytes; optional schema-backed JSON objects | raw bytes by default, typed values if pre-decode is enabled |
| Metrics (9003) | Prometheus text | scrape endpoint |
| Streaming status (9004) | gRPC event stream | low-rate health and state updates |
| Heatmap / spectrum (9005) | Binary or JSON | visualization and analysis |
Central source selection is a routing decision outside the node. The node emits the configured formats; the central server decides which stream is preferred downstream.
1.1 Endpoint examples
Radio configuration
Request:
POST /radio/config
{
"frequency_hz": 5400000000,
"sample_rate_sps": 40000000,
"gain_db": 32,
"band": "C",
"modulation_hint": "QPSK"
}Response on success:
{
"status": "accepted",
"command_id": "radio-cfg-001",
"effective_frequency_hz": 5400000000,
"effective_sample_rate_sps": 40000000,
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}Beam pointing
Request (azimuth/elevation):
POST /pointing/az-el
{
"azimuth_deg": 45.5,
"elevation_deg": 30.2,
"tracking_mode": "hold"
}Request (coordinate-based):
POST /pointing/target-coordinate
{
"target_latitude": 28.4,
"target_longitude": -80.6,
"target_altitude_m": 5000,
"reference_frame": "WGS84",
"timestamp_utc": "2026-03-17T14:30:00Z",
"tracking_mode": "continuous"
}Response (both):
{
"status": "accepted",
"command_id": "pointing-001",
"computed_azimuth_deg": 45.6,
"computed_elevation_deg": 30.1,
"range_estimate_m": 4800,
"node_gps_position": {
"latitude": 28.5,
"longitude": -80.5,
"altitude_m": 15,
"fix_quality": "RTK_FIXED"
},
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}Frame and payload configuration
Request:
POST /frame/config
{
"payload_id": "fc_primary",
"sync_words": [
{
"value_hex": "0x1ACFFC1D",
"length_bits": 32,
"confidence_threshold": 0.95
}
],
"frame_structure": {
"header_bytes": 8,
"max_payload_bytes": 256,
"crc_type": "CRC16_CCITT",
"fec_type": "VITERBI_1_2"
},
"predecode_enabled": true,
"field_schema": [
{
"path": "latitude",
"type": "float32",
"offset_bytes": 0,
"length_bits": 32,
"endianness": "little",
"scale": 1.0,
"offset_value": 0.0,
"unit": "deg"
},
{
"path": "speed_mps",
"type": "uint16",
"offset_bytes": 12,
"length_bits": 16,
"endianness": "little",
"scale": 0.1,
"offset_value": 0.0,
"unit": "m/s"
}
],
"output_mode": "decoded",
"timestamp_format": "node_absolute"
}Response:
{
"status": "accepted",
"command_id": "frame-cfg-001",
"payload_id": "fc_primary",
"effective_sync_words": 1,
"predecode_enabled": true,
"frame_definitions_loaded": 1,
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}Stream control
Request:
POST /stream/start
{
"stream_type": "telemetry",
"output_format": "json",
"target_url": "ws://central-server:9000/node-stream",
"payload_ids": ["fc_primary"]
}Response:
{
"status": "streaming",
"stream_id": "stream-001",
"stream_url": "ws://node:9001/stream-001",
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}Capability and status queries
Request:
GET /capabilitiesResponse:
{
"node_id": "node-001",
"antenna_type": "phased_array",
"antenna": {
"bands_supported": ["C"],
"frequency_range_hz": [4900000000, 6000000000],
"max_bandwidth_hz": 40000000,
"beam_modes": ["az_el", "coordinate", "fixed", "scan"]
},
"pointing": {
"az_range_deg": [-180, 180],
"el_range_deg": [0, 90],
"slew_rate_deg_per_sec": 10,
"positioning_accuracy_deg": 0.1,
"actuation_type": "phased"
},
"gps": {
"available": true,
"current_fix_quality": "RTK_FIXED",
"position": {
"latitude": 28.5,
"longitude": -80.5,
"altitude_m": 15
}
},
"rf": {
"channels": 1,
"rx_capable": true,
"tx_capable": false,
"sample_rates_supported": [10e6, 20e6, 40e6]
},
"dsp": {
"fec_types": ["VITERBI", "LDPC"],
"modulations_supported": ["BPSK", "QPSK", "PCM_FM"],
"doa_algorithm": "MUSIC"
},
"features": {
"beam_sweep": true,
"multi_beam": false,
"heatmap": true,
"multi_emitter": false,
"auto_tracking": true
},
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}Request:
GET /statusResponse:
{
"node_id": "node-001",
"online": true,
"last_heard_us": 1234567890123456,
"uptime_seconds": 86400,
"system": {
"cpu_load_percent": 45,
"memory_used_percent": 65,
"fpga_load_percent": 72,
"temperature_c": 52
},
"rf_chain": {
"frequency_hz": 5400000000,
"sample_rate_sps": 40000000,
"gain_db": 32,
"rssi_dbm": -65
},
"beam": {
"current_azimuth_deg": 45.5,
"current_elevation_deg": 30.2,
"requested_azimuth_deg": 45.5,
"requested_elevation_deg": 30.2,
"tracking_mode": "hold",
"tracking_quality": 0.92
},
"demodulator": {
"lock_state": "frame_lock",
"snr_db": 18.5,
"frequency_offset_hz": 1200,
"symbol_rate": 500000
},
"streams": {
"active_count": 1,
"total_packets_sent": 42000,
"dropped_packets": 3
},
"gps": {
"fix_quality": "RTK_FIXED",
"satellites_used": 18,
"hdop": 0.5,
"position": {
"latitude": 28.5,
"longitude": -80.5,
"altitude_m": 15
}
},
"timestamp_node_us": 1234567890123456,
"timestamp_central_us": 1234567890123456
}1.2 Multiple payload and sync-word configuration
The node should support one or more payload streams per RF channel.
Each payload stream is defined by:
- payload ID
- one or more sync words (for protocol detection)
- frame structure and size
- error correction type
- decoder type
Sync-word strategy:
- If the channel carries a single protocol, one sync word is sufficient.
- If the channel carries multiple protocols or payloads (e.g., flight computer and payload data packets), use multiple sync words with unique patterns.
- Each sync word should have a confidence threshold to avoid false locks.
Example multi-payload configuration:
POST /frame/config
{
"profiles": [
{
"payload_id": "flight_computer",
"sync_words": [
{
"value_hex": "0x1ACFFC1D",
"length_bits": 32
}
],
"frame_structure": {
"header_bytes": 8,
"max_payload_bytes": 256,
"crc_type": "CRC16_CCITT"
}
},
{
"payload_id": "experiment_data",
"sync_words": [
{
"value_hex": "0xF8726B19",
"length_bits": 32
}
],
"frame_structure": {
"header_bytes": 4,
"max_payload_bytes": 512,
"crc_type": "CRC32"
}
}
],
"output_mode": "decoded",
"routing_mode": "by_payload_id"
}The node's frame decoder should:
- monitor the demodulated bit stream for any configured sync word
- when found, attempt to decode the frame using the corresponding payload definition
- emit telemetry events tagged with the payload ID
- deliver each payload stream separately to the central server
TM operators must be able to define payload typing, not only sync words and frame length. At minimum, each payload definition should support:
- field schema (name, type, byte offset/length, endianness, scaling, unit)
- optional validation limits and enum/bitfield decoding
- stable parameter labels and identifiers that OPM/CM can bind later
- stable parameter labels and identifiers that OPM (Operation Manager) / CM (Configuration Manager) can bind later
Detailed parameter typing model
- A payload type contains multiple parameters (for example:
latitude,longitude,altitude_m,speed_mps,vertical_speed_mps,heading_deg,battery_v). - Each parameter must declare:
path,type,offset,length_bits,endianness,scale,offset_value,unit, and optionalvalid_range. - Derived parameters are allowed (for example, convert raw centimeters to meters) but must be explicitly declared and versioned.
- Schema versions must be immutable once deployed to an active mission session.
Per-target dashboard binding model (OPM (Operation Manager) / CM (Configuration Manager)-managed)
- Binding key should be
(target_id, payload_type_id, parameter_path). - One decoded parameter may bind to multiple UI parameters (for example
speed_mpsto both gauge and trend chart). - UI must support fallback binding scopes in this order:
- exact
target_idbinding - payload-type default binding
- global mission default binding
- exact
- If no binding exists, parameter remains available in an unbound telemetry inspector and must not be dropped.
The normalized decoded event sent to UI services should include at least:
target_idpayload_type_iddecoded_valuesas typed fieldsqualitymetadata (lock/CRC/confidence)- node and central timestamps
Recommended decoded payload shape:
{
"latitude": {
"value": 28.4,
"unit": "deg",
"quality": "ok"
},
"speed_mps": {
"value": 1200.5,
"unit": "m/s",
"quality": "ok"
}
}1.3 Node status fields
Node status should include at least:
- node identity
- node time and synchronization state
- online or degraded state
- configured frequency and sample rate
- active beam mode
- current beam azimuth and elevation
- tracking mode and track quality
- FPGA load or thermal indicators
- demodulator lock state
For antenna-type agnostic operation, status should also include:
- antenna type
- actuation type: phased, motorized, fixed
- capability version
- current control authority source
- GNSS position and fix quality
1.4 Live telemetry event shape
Every telemetry event delivered to the central server should carry enough metadata to be replayed and traced. A normalized event should include:
- event timestamp from node and central receive time
- mission ID
- node ID
- emitter or source ID when known
- payload or stream ID when known
- frame counter or sample sequence
- signal metrics such as RSSI, SNR, frequency offset, lock state
- decoded payload or binary frame reference
1.5 Stream modes
The node and central server should support these logical stream modes:
| Mode | Description | Main consumer |
|---|---|---|
telemetry | decoded objects for mission operations | frontend, mission services |
frames | encoded binary frames | recorders, protocol tools |
metrics | RF and node health | monitoring and UI |
events | state changes and anomalies | timeline, alerting |
iq_snapshot | optional short captures | engineering and debugging |
1.6 Error and fallback semantics
The contracts should support explicit machine-readable states such as:
searchingcarrier_locksymbol_lockframe_lockdegradedlost_lockover_temperaturetracking_unreliablesource_rejected
Add actuator and capability errors:
motion_limit_violationunsupported_modecapability_mismatchgps_fix_unavailableactuator_faultfrequency_hold_lostdoppler_out_of_range
The frontend must not infer these states from raw metrics alone.
1.7 Capability response shape
The capabilities response should include these top-level groups:
- node metadata
- antenna metadata
- RF metadata
- pointing metadata
- telemetry stream metadata
- health metadata
This allows central and UI to adapt to mixed node fleets without hard-coding antenna models.
1.8 Data sourcing recommendations
To keep the system lightweight and avoid unnecessary software, follow these sourcing rules:
On the antenna node:
- Local GNSS position and altitude: use directly from GNSS receiver for coordinate steering. This allows beam tracking to persist during brief central server outages.
- Frequency plan and mission timeline: pull from central on startup and on explicit request. Cache locally for fallback.
- Rocket telemetry history or state: do not replicate on the node. The node is a sensor, not a mission record keeper.
- Calibration tables and beamforming coefficients: store locally after download from central. Operator should be able to push updated calibration to the node without rebuilding.
On the central server:
- Node capabilities: fetch once on discovery, cache, and refresh on explicit requery or timeout.
- Node status and metrics: pull continuously via REST polling or subscribe to a push stream from nodes.
- Telemetry events from all nodes: aggregate in a message bus or event log.
- External data (weather, range safety, operator roles): pull from external systems on startup and maintain subscription or periodic fetch.
In the frontend:
- Do not replicate node status or raw telemetry. Subscribe to central server WebSocket streams.
- Do not compute beamforming weights or geodesy. Request computed results from the node or central.
- Cache mission profile, frequency plans, and UI configuration locally for offline access, but do not treat cached data as truth during active operations.
- Display source freshness: always indicate whether data is live, from replay, or stale.
Next: See Ground Station Frontend Architecture for frontend design and page layouts, or Ground Station Control Surface Design for control-surface patterns.