Skip to content

Local hardware-in-the-loop (HITL) test bench: closed-RF FT8/WSPR loopback drivable by a local /goal #171

Description

@ringof

Summary

A local, closed-RF hardware-in-the-loop bench that exercises the full real
signal path of this firmware and is drivable by a local Claude Code /goal:

QDX (TX, known message) -> attenuators -> RX888 -> FX3 firmware (this repo)
  -> radiod (rx888.so) -> decoder -> decoded message -> single pass/fail line

Design + plan: docs/local-hwil-plan.md. Each rung is structured so the
operator independently verifies it (re-runs the check / observes their own
gear); the verdict line is a check, not a claim.

Why

The firmware already has strong off-hardware coverage (CI builds arm-none-eabi;
tests/ has host-unit tests). Missing: verification that exercises the real
ADC/clock/GPIF/USB path + the radiod driver against an actual signal.

Goal ladder (status)

Each rung is a single /goal (one verdict line + exit code) and a commit
under this issue. "HW" = which hardware it needs (off-hardware rungs run in CI).

  • G0 — off-hardware FT8 and WSPR audio encode→decode self-tests + audio generators (PR docs: local hardware-in-the-loop FT8/WSPR bench plan (#171) #172). Every run writes rendered audio to out/ so the operator can independently decode it (jt9, wsprd).
  • 1 — HITL docker image built FROM the ka9q-radio image; reproduces the existing smoke-test output. (RX888)
  • 2a — QDX CAT control: set freq / key PTT / read response. (QDX)
  • 2b — QDX audio path: TX audio injected via USB soundcard. (QDX)
  • 3 — CW carrier ~10 MHz visible in powers: peak within 300 Hz, ≥20 dB over noise. (RX888+QDX+RF, DANGER pre-gate)
  • 4 — FT8 decode automation in hosted CI (uses G0). (none)
  • 5 — TX/RX FT8 end-to-end over the bench. (RX888+QDX+RF, DANGER pre-gate)
  • 6 — WSPR decode automation in hosted CI. (none)
  • 7 — TX/RX WSPR end-to-end over the bench. (RX888+QDX+RF, DANGER pre-gate)

Tooling (validated off-hardware)

  • FT8: gen_ft8 / decode_ft8 (ft8_lib@9fec6ca); independent cross-decoder jt9 (WSJT-X) for operator verification.
  • WSPR: wsprsimwav (wspr-cui@839b86f) renders 48 kHz audio; wsprd decodes.
  • sox for sample-rate/format conversion only (48k↔12k) — never signal synthesis.

Real encoders/decoders own the protocol; encoder independence was intentionally
waived for generation, with operator-side cross-decode as the independent check.

RF safety pre-gate (rungs 3/5/7)

Each transmit-into-RX888 rung prints a red DANGER!! banner before keying.
The first attenuator stage must be power-rated for full QDX output (~+37 dBm);
the operator confirms the RX888-input level is in range. Operator pre-flight —
gates the rung, not discovered by it.

Operator interface contract (must be supplied)

  • Total attenuation dB + first-stage power rating; target level at RX888 input.
  • QDX: ALSA device, CAT serial port/baud, band/dial freq, drive level, fw version.
  • RX888/radiod: band(s), sample-rate/center config.
  • Pass criteria: test callsign/grid, freq tolerance (Hz), min SNR, window, N-of-M quorum.

Out of scope (separate plan)

Remote self-hosted GitHub Actions runner, Actions/cron wiring, and the runner
security model (untrusted-fork code on a machine wired to a transmitter).

Not validated by this bench

A wired bench validates the firmware + software path at a controlled level. It
does not validate antenna, propagation, or real-world RFI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions