Running the radio validation matrix¶
rigplane validate exercises a radio's CAT control surface against the
capabilities its profile declares, and reports — per check — whether the radio
actually does what the profile claims. It is a debugging instrument: after a
run, every fail is a real discrepancy between the profile and the hardware, not
a quirk of the tool.
The check list is generated from the radio's profile (rigs/<model>.toml) via
the capability→check-spec registry — you do not hand-author a template. Pass
--model <MODEL> and the matrix is built in memory from that profile's declared
capabilities.
Safety first. Hardware execution is opt-in and read-modify-verify-restore (RMVR): every write reads the original, writes a different value, verifies the readback, and always restores the original. TX (PTT) and the antenna tuner are never auto-actuated — they report
blocked/manual_requiredunless you explicitly authorize them, and even then they are operator-verified, never keyed by the tool. Unknown/undeclared controls default to read-only.
Quick start¶
# Dry-run (no hardware): generate + plan the matrix from the profile
uv run rigplane --model IC-7610 validate
# Against real hardware, read-only (safe: all writes SKIP, only reads run)
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 uv run rigplane \
--model IC-7610 validate --hardware --allow-hardware --read-only
# Against real hardware, write-enabled (RMVR writes with restore; no TX/tuner)
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 uv run rigplane \
--model IC-7610 validate --hardware --allow-hardware
--hardware and --allow-hardware and the env var
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 must all be present to touch a radio; any
one missing keeps the run a dry-run plan. This triple gate is deliberate.
Add --json --output report.json for a machine-readable artifact, or omit --json
for a human-readable summary.
Recipes¶
Xiegu X6200 — serial¶
Control port /dev/cu.usbmodem58910181093 @ 19200 baud, CI-V address 0xA4.
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 uv run rigplane \
--backend serial --serial-port /dev/cu.usbmodem58910181093 --serial-baud 19200 \
--model X6200 --radio-addr 0xA4 --timeout 6 \
validate --hardware --allow-hardware --provider native \
--json --output /tmp/x6200.json
Start with --read-only (above, drop the flag to enable RMVR writes). The X6200's
RIT, XIT, and manual-notch controls accept a SET but never answer the GET; they are
declared write-only in rigs/x6200.toml and validated with set-and-observe
(set a test value, confirm it is accepted with no NAK/timeout, restore a benign
default) rather than a read-back that would falsely time out.
The serial port is exclusive. If RigPlane Pro (or any other client) holds it you will see
Commander stopped/multiple access on port. Close the other client first — check withpgrep -fl usbmodem58910181093.
Icom IC-7610 — LAN¶
Address 0x98, control port 50001. Supply host/user/password via flags, env
(ICOM_HOST / ICOM_USER / ICOM_PORT), or --pass-file (avoids exposing the
password in ps). Never commit credentials.
ICOM_HOST=192.168.55.40 ICOM_USER=<user> ICOM_PORT=50001 \
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 uv run rigplane \
--backend lan --pass-file /path/to/secret \
--model IC-7610 --radio-addr 0x98 --timeout 6 \
validate --hardware --allow-hardware --provider native \
--json --output /tmp/ic7610.json
Flags¶
Global (before the validate subcommand):
| Flag | Purpose |
|---|---|
--model <NAME> |
Radio model → selects the profile the matrix is generated from. |
--backend {lan,serial,yaesu-cat,rigctld} |
Transport (auto-inferred from --serial-port). |
--serial-port / --serial-baud |
Serial device + baud. |
--host / --control-port / --user / --pass-file |
LAN connection (env: ICOM_HOST/ICOM_PORT/ICOM_USER). |
--radio-addr |
CI-V address. |
--timeout |
Per-operation timeout (s). |
validate subcommand:
| Flag | Purpose |
|---|---|
--template <path> |
Optional. Use a hand-authored template instead of generating from --model. |
--hardware |
Execute against the radio (otherwise a dry-run plan). |
--allow-hardware |
Second gate; with RIGPLANE_VALIDATION_ALLOW_HARDWARE=1, permits hardware writes. |
--read-only |
Run reads only; every write check is SKIP. |
--tx-allowed / --tuner-allowed |
Authorize the TX/tuner checks (still operator-verified, never auto-keyed). |
--provider {native,hamlib} |
native = RigPlane's own backend; hamlib = via the rigctld bridge. |
--compare <artifact.json> |
Diff this run against a prior artifact. |
--gate <golden.json> |
Gate this run against a golden normalized artifact: exit 1 on any regression. |
--regen-golden <path> |
Write the normalized artifact to <path> as a new --gate golden baseline. |
--operator-id <id> |
Record the operator in the artifact. |
--json / --output <path> |
Emit JSON (to stdout or a file) instead of the human summary. |
Golden gate: dry-run regression protection¶
A golden is a normalized dry-run artifact (volatile fields stripped)
committed to the repo. Gating a run against it turns the validation matrix into
a regression check: any check that goes pass→fail, any new fail/blocked
check, any missing check, or any declaration drift exits 1. Improvements
never block.
Committed goldens live in tests/golden/validation/. Today only the IC-7610
dry-run golden is committed (ic7610.dry-run.json); X6200 and FTX-1 goldens
are a known loose end.
Run the dry-run gate¶
uv run rigplane --model IC-7610 validate --provider native --dry-run \
--gate tests/golden/validation/ic7610.dry-run.json
Exit 0 = no regression; exit 1 = regression (a diff summary is printed to
stderr); exit 2 = golden missing/unreadable.
Regenerate a golden after an intentional matrix change¶
When you intentionally change the registry, a profile's declared capabilities,
or an override file, the gate (and
tests/test_validation_gating.py::test_committed_ic7610_golden_matches_live_dry_run)
will fail until the golden is refreshed:
uv run rigplane --model IC-7610 validate --dry-run \
--regen-golden tests/golden/validation/ic7610.dry-run.json
Review the resulting diff carefully before committing — the golden is the contract; regenerating it to silence a gate failure you do not understand defeats the purpose.
CI¶
.github/workflows/quick.yml runs the IC-7610 native dry-run gate on every
push/PR that touches src/**, tests/**, etc. — so a registry or
profile-declaration regression fails CI without any hardware. It is dry-run
only and takes under a second.
Pre-release validation runner¶
scripts/validate-release.sh bundles the pre-release validation pass across
the owned radios (IC-7610, X6200, FTX-1).
Default (CI-safe, no hardware):
For each radio it runs the native dry-run matrix; where a committed golden
exists it gates against it, otherwise it notes the missing golden (with the
exact --regen-golden command) and still verifies the matrix generates. Exits
non-zero on any gate regression or matrix-generation error; skips never fail
the run.
Hardware (opt-in, pre-release):
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 \
ICOM_HOST=192.168.55.40 ICOM_USER=<user> ICOM_PASS_FILE=/path/to/secret \
X6200_SERIAL_PORT=/dev/cu.usbmodem58910181093 \
FTX1_SERIAL_PORT=/dev/cu.usbserial-XXXX \
scripts/validate-release.sh --hardware
Hardware execution requires both the --hardware script flag and
RIGPLANE_VALIDATION_ALLOW_HARDWARE=1 (the script then passes the CLI's own
--hardware --allow-hardware gates through). Each radio additionally needs
its connection environment — ICOM_HOST/ICOM_USER/ICOM_PASS_FILE for the
IC-7610, X6200_SERIAL_PORT (optionally X6200_SERIAL_BAUD, default 19200)
for the X6200, FTX1_SERIAL_PORT (optionally FTX1_SERIAL_BAUD, default
38400) for the FTX-1. Radios without configuration are skipped with a note, so
you can validate whatever subset is on the bench. Hardware artifacts are
written to /tmp/<radio>-hw-<timestamp>.json; the per-radio invocations match
the recipes above (RMVR writes with restore, TX/tuner never auto-actuated).
Reading the results¶
Each check reports one status:
| Status | Meaning |
|---|---|
pass |
The control behaved as the profile declares (readback matched, or — for write-only controls — the SET was accepted). |
fail |
A real discrepancy: the control did not react, the readback disagreed, or a command errored/timed out. This is a bug to investigate. |
unsupported |
The capability is not declared for this radio (or the radio lacks the operation) — recorded, not hidden. |
manual_required |
Operator-verified out of band (e.g. RX audio, scope, and the never-auto-actuated TX/tuner). |
blocked |
A TX-adjacent check that was not authorized (--tx-allowed/--tuner-allowed). |
skip |
Skipped — typically a write check under --read-only. |
Every check carries an evidence object with the concrete values observed
(original / changed / readback for RMVR; verification: set_observe +
set_accepted for write-only controls). Use it to see exactly what the radio did.
Because the matrix is exhaustive over the profile's declared capabilities, a clean
run with no fail means the profile and the hardware agree. A fail points at a
real defect — a missing or mis-routed CI-V command, a stale readback, or a control
the profile over-claims.
See also¶
docs/contracts/validation-matrix-v1.md— the versioned artifact/template schema.docs/plans/2026-05-28-universal-validation-matrix.md— the profile-driven matrix design (registry, generators, comparison, converter).