feat(webconfig): let the portal discover board-specific CLI commands

The FEM commands moved out of CommonCLI into Board::handleCommand(), and
the T-Beam 1W fan control was added there too, but the portal never caught
up: `radio.fem.txgain` and the fan commands were missing from the terminal
table entirely, while `radio.fem.rxgain` was offered on every board even
though a board with no hook now answers "??:" rather than "unsupported".

Whether a node answers these is a property of the board, not of the build,
so the page cannot know from the firmware version. Ask the board instead:
probeBoardCommands() runs each candidate getter once on the loop task at
startup and keeps the ones that answer, which needs no per-variant list
because Board::handleCommand() already reports whether it handled a
command. /api/status names the survivors and the page hides everything
else, so adding a command to a variant means one entry in WC_BOARD_CMDS
rather than an edit per board.

The two FEM keys also become Radio-panel toggles, gated the same way;
their `set` reaches the board hook through the existing config batch, so
only the allowlist and /api/config needed to grow.

webconfig_cli_audit.py now scans variants/*/*Board.cpp for the boards that
actually build the portal, checks set-only keys in the reverse direction,
and verifies every gate is a command some board answers; the mock gained
--board-cmds so both shapes of board are testable. `stop ota` joins
NOT_OFFERED: `start ota` cannot run from the portal, so it has nothing to
stop.
This commit is contained in:
agessaman
2026-09-19 20:51:25 -07:00
parent 4dca3688ea
commit 3d2d0b5da6
8 changed files with 319 additions and 41 deletions
@@ -10,6 +10,8 @@ TEST(WebConfigKeys, AllowsKnownScalarKeys) {
EXPECT_TRUE(wcIsAllowedSetKey("name"));
EXPECT_TRUE(wcIsAllowedSetKey("radio"));
EXPECT_TRUE(wcIsAllowedSetKey("repeat"));
EXPECT_TRUE(wcIsAllowedSetKey("radio.fem.rxgain")); // Board::handleCommand(), not CommonCLI
EXPECT_TRUE(wcIsAllowedSetKey("radio.fem.txgain"));
EXPECT_TRUE(wcIsAllowedSetKey("wifi.ssid"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.iata"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.neighbors"));