All docsStellar LinkRF bench · by Stellar Systems v0.1.0

Control & Automation

Assertions and Reports

How a scenario run is judged: assertion types, measurable metrics and aggregates, windows, verified versus passed, and the structure of a run report.

A scenario's assertions decide whether a run passed. SatLink separates two questions for every assertion: did the condition hold (passed), and does the verdict rest on something the board actually measured (verified)? A run passes only if its assertions pass and are verified.

Assertion types#

typePasses whenVerified when
metric_thresholdA metric, reduced over the run or a window, compares true against valueThe metric was measured on live PL registers
packet_seenAt least min_count (default 1) packets were recovered from the RX streamThe datapath was attached and acquired, so the RX side was really watched
packet_seen_after_eventA packet was recovered within within after the event named by event_refThe datapath was attached and acquired

metric_threshold#

YAML
- name: "the_mute_actually_cut_the_link"
  type: "metric_threshold"
  metric: "rx.lock.max"
  operator: "<="
  value: 0.0
  during:
    step: "link_down"
    settle: "3s"
FieldDefaultMeaning
metric—What to measure (below)
operator<=<=, >=, <, >, == (or =)
value0The threshold. Decimal or hex (value: 0xFF01), so a register value is written once
duringwhole runThe interval the metric is reduced over (see Windows)

packet_seen and packet_seen_after_event#

YAML
- name: "the_isolated_telecommand_came_back_whole"
  type: "packet_seen"
  min_count: 1

- name: "tm_after_tc"
  type: "packet_seen_after_event"
  event_ref: "tc"          # the id of a timeline step
  within: "5s"

packet_seen counts packets recovered from the receive stream, not packets the scenario sent or expected. Its result distinguishes three states:

StateResult
No datapath in the daemonNot verified: nothing watched the link
Datapath present but not acquired when the run startedNot verified, and not a statement about the link. Enable the datapath (or let chain conditioning do it)
Datapath watchedVerified; passes or fails on the count

Measurable metrics#

NamespaceMetricsSource
rx.*evm, snr_db, mer_db, ber, ber_asm, cfo_hz, phase_err, rssi_dbm, part_rssi_db, part_rx_gain_db, agc_gain, lock, carrier_lockThe telemetry samples collected during the run (see Telemetry and Metrics)
chan.*ctrl, atten, awgn_amp, nco_freq, burst_rateThe channel emulator's active bank, read back on every sample
reg.<name>Any register listed in [telemetry] watch_registersSampled on the same tick as the other metrics
rf.*ber, frame_error_rate, snr_db, doppler_hzModel values computed from what the scenario asked for. Never verified

Each measured metric takes an optional aggregate suffix:

SuffixValue
.last (default)The last sample in the window
.min, .max, .meanOver the samples in the window
.deltamax − min: how much a cumulative counter moved during the window

Notes on specific metrics:

  • rx.lock and rx.carrier_lock are booleans exposed as 0/1, so .mean is the fraction of samples locked: "stayed locked" is rx.lock.mean >= 1.0, "never locked" is rx.lock.max <= 0. lock is the timing loop; carrier_lock is the carrier loop, a different fact.
  • Frame counters are cumulative and never reset between profiles. Use reg.rx_demod.deframer.frame_cnt.delta (frames delimited during the window) and reg.tx.framer.frame_cnt.delta (frames transmitted); .max would pass on counts from an earlier run.
  • rx.agc_gain.max <= 15 says the receiver was not starved. Without it, a starved run and a healthy one produce otherwise identical reports.
  • rx.evm reads the QPSK or BPSK demodulator; on a GMSK profile it is meaningless. Assert on frame counters, or on reg.rx_demod.demod_gmsk.status.
  • A register not listed in watch_registers is unresolvable, not zero, and the assertion is reported not verified. Scenarios cannot add registers to the watch list; the daemon's configuration does.

An unknown metric name is a scenario error: the assertion fails, not verified, and the message lists the measurable metrics.

Windows#

Without during, a metric is reduced over the whole run, and an assertion can rarely test the condition it is named after: "the mute released the lock" becomes "the lock dropped at some point", which a single dropout anywhere, or an inert channel, satisfies. Give assertions a window.

YAML
during: { step: "feed", settle: "4s" }       # the window of a timeline step
during: { from: "3s", to: "4s" }             # explicit bounds
during: { from: "30s" }                      # to the end of the run
FieldMeaning
stepA timeline step's id. A span (from/to) gives its window; an instant step (at) opens a window that lasts until the next step starts, or to the end of the run
from, toExplicit bounds; to is exclusive and optional
settleTime to ignore after the window opens, for the effect to establish itself

step and from/to cannot be combined. A window that names no step, is empty, or is entirely consumed by its settle makes the assertion fail not verified, never falls back to the whole run. A window in which no telemetry sample falls is likewise not verified.

Passed, verified, and the run verdict#

Assertion resultCounts toward a pass?
Passed and verifiedYes
Failed and verifiedNo: the board measured a failure
Not verified (model metric, no sampler, stub samples, empty window, no datapath)No: "we did not look" is not evidence

pass_criteria.mode: all (the default) requires every assertion to pass and be verified; any requires one. A scenario with no assertions never passes.

A telemetry sample taken while no bitstream answers (every register reads zero) is marked stub; an assertion resting only on stub samples is not verified.

The run report#

GET /api/v1/reports/{run_id} (also GET /api/v1/scenario-runs/{run_id} for the summary) returns the report of a finished run:

FieldMeaning
run_id, scenario_name, profile_refWhat ran
statusrunning, passed, failed, cancelled
started_at, ended_at, duration_msTiming (board clock, see below)
steps_executed, assertions_passed, assertions_failedCounts
verifiedWhether any assertion read the link rather than the script
abortedThe run did not reach the end of its timeline
sourceWhere the numbers came from: pl, stub, mixed or none
eventsThe event log, in time order
assertionsOne result per assertion: name, passed, verified, message
metric_samplesEvery telemetry sample of the run (see Telemetry and Metrics)
sample_interval_msThe sampler's cadence; absent means no sampler was attached

An assertion message shows what was measured, for example:

text
[measured, 105 samples over 7.0s..25.0s (after 4.0s of settling)] rx.agc_gain.max = 9.4580e0  <= 1.5000e1 → PASS

Events#

kindMeaning
scenario_start, scenario_endRun boundaries
profile_appliedThe profile was applied (and the channel reset to pass-through)
channel_set, channel_ramp, channel_curveA channel change reached the datapath
channel_stagedA channel change was written but not committed: it had no effect
receiver_cleared, receiver_clear_unavailableNo longer emitted. Older reports carry them: the engine used to flush the receiver 500 ms into a traffic step. The PL now re-arms the receiver itself, at each profile commit and at each burst onset
traffic_started, traffic_stoppedThe built-in feed, with what it sent and how many packets
traffic_refusedA feed was asked for and did not start: the run measured silence
packet_injectedAn inject_packet step ran
packet_transmittedIts bytes were actually queued on the datapath (with the egress state)
packet_expected, packet_observedAn expect_packet step, and a matching packet actually recovered
link_opened, link_unavailableScenario endpoints bound, or not
tone_on, tone_refusedA tone was transmitted, or refused

Read the event log of every new scenario's first run: a *_staged, *_refused or *_unavailable event means the timeline did not do what the file says.

Time in reports#

Run times (started_at, frame times) come from the board's clock, which starts at 1970 at every power-on unless it has been set (see Hardware Setup). Event and sample times are elapsed_ms since the start of the run, which is always reliable.

Keeping reports#

The run store is on the board's RAM disk: reports are lost at reboot. Save the ones you need:

Shell
curl -s $SATLINK_API/api/v1/reports/<run-id> > report-<run-id>.json

The web console's report page can export a report as JSON too. See Analysis & Reporting.

Stellar Link · v0.1.0

↑↓ to moveEnter to open