159 lines
8.2 KiB
Markdown
159 lines
8.2 KiB
Markdown
# The `test` command — hardware self-test suite
|
|
|
|
`test` runs modular hardware self-tests on the real gimbal, writes a plain-text
|
|
report to disk, and compares each run against a pinned baseline and the previous
|
|
run so both outright failures **and** slow drift are visible. It replaces the old
|
|
firmware-only `gimbal diag` (the firmware motor self-test is now one leaf inside
|
|
`test gimbal encoder`).
|
|
|
|
Implementation: [`TestRunner`](../src/core/TestRunner.cpp) (worker thread, mirrors
|
|
`PitchSetupRoutine`) + the pure [`TestReport`](../src/core/TestReport.cpp) model
|
|
(`formatTestReport`/`parseTestReport`/`applyComparison`) and
|
|
[`HostMetrics`](../src/core/HostMetrics.cpp) host reads.
|
|
|
|
## Usage
|
|
|
|
```
|
|
test [<subsystem> [<leaf>]] [<profile>]
|
|
test list # list configured profiles
|
|
test baseline # pin the most recent report from this session as the baseline
|
|
```
|
|
|
|
The command is a three-level hierarchy; omitting a level widens scope. A trailing
|
|
token that names a configured profile selects the profile at any position.
|
|
|
|
| Command | Runs |
|
|
|---------|------|
|
|
| `test` | everything |
|
|
| `test gimbal` | all gimbal leaves |
|
|
| `test gimbal homing` | just homing |
|
|
| `test imu drift` | just IMU yaw drift |
|
|
| `test host` | host thermal/disk/memory/load (no hardware needed) |
|
|
| `test hardware` | is this rig the one `hardware.ini` declares? (no homing needed) |
|
|
| `test gimbal encoder strict` | one leaf, `strict` profile |
|
|
| `test strict` | everything, `strict` profile |
|
|
|
|
While a test runs, interfering commands (moves, homing, a second test, capture,
|
|
`refresh`) are ignored; **Esc** (or `cancel`, or `x`) cancels. Pure-UI keys (`p`, `i`, `c`,
|
|
`g`, `?`) keep working.
|
|
|
|
## Subsystems and leaves
|
|
|
|
| Subsystem | Leaf | Checks |
|
|
|-----------|------|--------|
|
|
| `gimbal` | `homing` | endstop-count + travel + homing-time reproducibility |
|
|
| `gimbal` | `encoder` | firmware DIAG tracking quality (repeated) + hold-corrector drift |
|
|
| `gimbal` | `friction` | full-range traverse time + motor load per direction |
|
|
| `gimbal` | `backlash` | reversal dead-band (encoder error) at small steps |
|
|
| `gimbal` | `balance` | pitch up-vs-down per-step peak current (center-of-mass symmetry) |
|
|
| `imu` | `config` | device identity, output mode, sample rate, active XKF profile |
|
|
| `imu` | `health` | rate, dropped samples, angle noise, accel-norm, temperature |
|
|
| `imu` | `drift` | yaw drift rate (deg/min) over a long static window |
|
|
| `host` | `thermal` | hottest CPU thermal zone (`/sys/class/thermal`) |
|
|
| `host` | `disk` | free space on the image partition (`statvfs`) |
|
|
| `host` | `memory` | available RAM / swap (`/proc/meminfo`) |
|
|
| `host` | `load` | load average vs CPU count |
|
|
| `hardware` | `axes` | the axes `[Hardware] axes` declares appear in telemetry, **and no others** |
|
|
| `hardware` | `motor` | `DUMP` echoes **everything** `hardware.ini` declares — current, `steps_per_rev`, `enc_deviation`, `sw_mode` and all three speeds — catching a stale flash, a lost `SET`, or an axis that never left `NEEDCFG`. Also checks the axis is not on the firmware's **limp-home** fallback, which `USE DEFAULTS` leaves looking fully configured |
|
|
| `hardware` | `imu` | a declared IMU is connected and a sample arrives |
|
|
| `hardware` | `env` | a declared ambient sensor sits on a **real I2C** adapter (not SMBus-only) and returns a CRC-valid reading |
|
|
| `hardware` | `camera` | every configured camera opened; `usbfs_memory_mb` and USB3 LPM hold |
|
|
|
|
Gimbal leaves require a homed gimbal (`home` first); IMU leaves require the IMU
|
|
(`[Features] enable_imu`). `host` needs neither, so `test host` doubles as a fast
|
|
non-intrusive smoke check.
|
|
|
|
**`hardware` deliberately does not need a homed gimbal either** — it reads telemetry
|
|
and `DUMP` and moves nothing, because it is what you run on a tower that has just
|
|
been installed and *before* you trust it enough to home it.
|
|
|
|
> Some metrics are most accurate with firmware support (a homing-report `HM` line
|
|
> and a load-capture `LP` primitive); until those land the modules fall back to
|
|
> sampling the `ST` telemetry stream and note it in the report. See
|
|
> [test-roadmap.md](test-roadmap.md).
|
|
|
|
### What `test hardware` is for
|
|
|
|
The other subsystems measure things and trend them. `hardware` asks a different
|
|
question — **is this rig the one `hardware.ini` says it is?** — so its metrics are
|
|
1/0 verdicts and the sentence that matters lives in the notes:
|
|
|
|
```
|
|
[hw_motor] FAIL 0 ms # firmware adopted the declared drivetrain
|
|
yaw_current = 0 FAIL
|
|
; yaw_current: yaw: firmware did not echo gs/irun/irun_home -- it predates the
|
|
CURRENT command, so whatever hardware.ini asks for is NOT being applied. Re-flash it
|
|
; pitch_current: not checked -- no [Motor.pitch] current declared
|
|
```
|
|
|
|
Three rules hold across every leaf:
|
|
|
|
- **Nothing declared reports `SKIP`, never `PASS`.** A check that did not happen
|
|
must not look like one that succeeded — that is how a precondition regression
|
|
survives a green run. An un-migrated tower with no `hardware.ini` skips the lot
|
|
and still reports `all_pass`.
|
|
- **Unreadable is a failure.** An empty or unparseable `usbfs_memory_mb` fails
|
|
rather than passing by default, for the same reason.
|
|
- **The detail names the fix, not the symptom.** "SMBus-only, point `[Env]
|
|
i2c_device` at a real adapter" rather than "sensor not responding" — the two
|
|
look identical from the sensor's side, and the distinction is what
|
|
known-issues #2 cost to learn.
|
|
|
|
The `motor` leaf reports three metrics per axis. `<axis>_not_limp` asks where the
|
|
values came from; `<axis>_current` covers `GLOBALSCALER`/`IRUN`/`homing IRUN`; and
|
|
`<axis>_declared` covers everything else the host owns. All three treat an
|
|
**absent** field as unconfirmable rather than as a mismatch — comparing against a
|
|
missing value would report "firmware has -1", which reads as a wrong setting and
|
|
sends someone to re-send a command that was never the problem.
|
|
|
|
`<axis>_not_limp` runs first because it explains the other two. `USE DEFAULTS` is
|
|
the deliberate bench escape from `NEEDCFG`, and it fills the firmware's `cfg_rx`
|
|
mask completely — so an axis running limp-home values leaves `NEEDCFG`, reports
|
|
`READY`, and passes every state check while running numbers that describe no real
|
|
drivetrain. Without this metric the only symptom is `<axis>_declared` listing six
|
|
mismatches, which reads as "host and firmware disagree" and sends someone to
|
|
re-send values that were never sent at all. The fix is a host restart, which
|
|
re-sends the whole declared block.
|
|
|
|
The `motor` leaf is the one worth running after every flash: `GLOBALSCALER` and
|
|
`IRUN` are **write-only** on the TMC5160, so the `DUMP` echo is the only evidence
|
|
a `CURRENT` command landed. A homing current that silently did not take is the
|
|
case that destroys an endstop.
|
|
|
|
| Key | Default | For |
|
|
|---|---|---|
|
|
| `[Test] usbfs_min_mb` | `1000` | What `usbfs_memory_mb` must reach. It reverts to 16 on every reboot unless it is on the kernel cmdline |
|
|
| `[Test] usb3_lpm_path` | *(empty)* | The camera port's `usb3_lpm_permit` node. Differs per board, so it cannot be guessed; empty means the LPM check reports "not checked" rather than passing |
|
|
|
|
## Profiles
|
|
|
|
A profile is a named bundle of repetition counts, sampling windows and pass
|
|
thresholds, configured in `config.ini` under `[Test]` and `[TestProfile.<name>]`
|
|
(see [configuration.md](configuration.md) and the example config). The default is
|
|
`standard` (5 reps). Unknown keys are ignored, so new tests add knobs without code
|
|
changes. Ship profiles like `quick` (fewer reps) and `strict` (more reps, tighter
|
|
drift gate).
|
|
|
|
## Pass / fail and comparison
|
|
|
|
Each metric can carry an **absolute threshold** from the profile. Independently,
|
|
every metric is compared to the **pinned baseline** and the **previous run**; a
|
|
move worse than `drift_warn_pct` is flagged, and if `drift_gates_pass = true` a
|
|
flagged metric also fails. A section fails if any of its metrics fail; the run
|
|
fails if any section fails.
|
|
|
|
## Reports
|
|
|
|
Each run writes `logs/test_YYYYMMDD-HHMMSS.log` (under the per-user log dir). The
|
|
format is human-readable and re-parseable:
|
|
|
|
```
|
|
[host_disk] PASS 0 ms # image partition free space
|
|
free_gb = 3.65 GB PASS base=3.66 prev=3.66 drift=-0.001%
|
|
used_pct = 2.65 % PASS
|
|
```
|
|
|
|
`test baseline` copies the most recent report to the baseline file
|
|
(`[Test] baseline_file`, default `<logdir>/test_baseline.txt`); subsequent runs
|
|
show `base=` columns against it.
|