8.2 KiB
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 (worker thread, mirrors
PitchSetupRoutine) + the pure TestReport model
(formatTestReport/parseTestReport/applyComparison) and
HostMetrics 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,
gimbal dump) are ignored; Esc (or cancel, or m) cancels. Pure-UI keys (f, s, 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
HMline and a load-captureLPprimitive); until those land the modules fall back to sampling theSTtelemetry stream and note it in the report. See 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, neverPASS. 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 nohardware.iniskips the lot and still reportsall_pass. - Unreadable is a failure. An empty or unparseable
usbfs_memory_mbfails rather than passing by default, for the same reason. - The detail names the fix, not the symptom. "SMBus-only, point
[Env] i2c_deviceat 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 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.