End-of-line test
One script, run on the finished robot, that checks every sensor, every actuator and the safety chain, then
writes a pass/fail report and the end_of_line entry for the robot's build record. It's the
last thing that happens to a robot before it's called built, and the first thing to rerun after a repair.
It uses only the robot's own sensors: wheel encoders, IMU, GNSS, lidar, battery. Nothing comes from the simulator's ground truth, so the script that passes in the simulator is the same script you run on the real robot.
make eol-run # in the simulator (a box is placed 3.5 m ahead for step 6)
make eol-run SERIAL=MB-0001 # the serial goes in the report and the build-record entry
On the robot: ros2 run mowlark_safety eol_test --ros-args -p serial:=MB-0001 -p blade:=false -p lidar:=false
(H2: blade disconnected, no lidar yet; those rows report SKIP).
Before you run it on a real robot
- About 4 m of clear, flat lawn ahead, nobody within 3 m, and a box (at least 20 cm tall) about 3 m in front of the robot.
- The test drives the robot and, unless
blade:=false, spins the blade. Keep a hand near the STOP button for the whole run. - Until gate 3 passes:
blade:=false, the blade off the motor and the connector taped.
What it checks
Every command goes through the safety gate, exactly as a mowing mission's would, so the test exercises the whole chain and not just the motors.
| Step | What the test does | Pass if |
|---|---|---|
| 1 sensors | Listens for 5 s with the robot still | IMU, GNSS, joint states, lidar and battery at ≥ 90% of their rates; gravity 9.81 ± 0.3 m/s²; gyro bias ≤ 0.02 rad/s; RTK fixed with σ ≤ 5 cm; battery a sane 7S voltage and ≥ 30%; STOP not latched |
| 2 wheels | Forward at 0.2 m/s, pivot left, pivot right; then turns back to the start heading on the gyro | Each wheel within 10% of the speed the command implies, with the right sign; IMU yaw rate matches within 15%. Catches a swapped motor, a reversed encoder, a wrongly mounted IMU |
| 3 blade | Spins up with the 3 s soft start, then commands it off | 90% speed within 4 s; below 10% within 2 s |
| 4 STOP | Drives at 0.3 m/s with the blade on and presses STOP while the driver keeps commanding motion | Standstill ≤ 1 s; blade below 10% ≤ 2 s; nothing moves for 3 s while latched; ring red; release (GO held 2 s) leaves it stopped |
| 5 watchdog | Drives with the blade on, then the driver goes silent | Drive stops ≤ 1 s; blade below 10% ≤ 3 s |
| 6 obstacle | Drives at the box and keeps asking to drive on | The safety gate stops it, ≥ 10 cm from the box |
| 7 manual | Lists what a script can't do | Lift, tilt, bumper, unplugging each interlock connector, hanging the microcontroller: an operator does these from gate 3 (hardware/safety.md) and signs each one on the build record |
The blade-stop limit (2 s) is our target, not a measured number: gate 3 measures the real motor's stop time and the limit is set from that, in writing, never quietly in code.
What it found in the simulator
The first run (eol-1) passed everything except the watchdog. When the driver went silent the robot simply carried on: Gazebo's drive plugin, like many real motor controllers, keeps executing the last command it got. Nothing in our software timed commands out. Now the safety gate does: a driver that wants motion has to keep saying so, every 0.5 s or less, or the gate sends zero.
The second run (eol-2) found something else: each time the blade was cut, the whole robot twisted by about 8°. The simulated blade motor had unlimited torque and stopped the blade within one 1 ms physics step; the reaction twisted the chassis. After STOP and the watchdog the robot pointed 16° off and drove past the box. The simulated motor is now torque-limited (±3 N·m), and the blade stops in 0.75 s, a believable number to test against.
| Run | Watchdog: drive stops | Blade stop (commanded) | Twist from the blade stops (ground truth) | Result |
|---|---|---|---|---|
| eol-1 | never | 0.01 s | 8° (one stop; the watchdog never cut the blade) | FAIL |
| eol-2 (watchdog) | 0.51 s | 0.01 s | 8° + 8° | FAIL (missed the box) |
| eol-3 (torque-limited blade) | 0.54 s | 0.75 s | < 0.1° | PASS |
Outputs
reports/<run_id>-eol.md: the table above, with every measured value.reports/<run_id>-eol.json: theend_of_lineblock of the build record (serial, run id, time, every check's result and measurement).- The recorded bag (
data/runs/<run_id>) and theeol_checktelemetry events in Elasticsearch, like every run.