Docs menu
Safety model
How the LB deadman and ackermann_mux each stop the car, which one protects you in each mode, and the rule every driving node must follow.
Two independent checks stand between driving code and the motor. The first is inside every node that can move the car: it publishes zero speed unless the driver is holding LB. The second is ackermann_mux, which lets manual commands override autonomy. In autonomy mode only the first one is active, which is why it is mandatory.
/drive. Check 2 runs in ackermann_mux, which receives /drive and /teleop and passes on only one of them as /ackermann_cmd. The label under each box is what that check does when it refuses.Check 1: the LB deadman
Every node that can move the car subscribes to /joy itself and refuses to drive unless both of these are true:
- LB (
deadman_button, index 4) is held in the latest/joymessage. - That message is newer than
joy_timeout_sec, 0.5 s.
The node runs this check first on every tick, before any other logic. When it fails, the node publishes speed 0. gap_follow holds the steering where it is, and pure_pursuit returns it toward centre at the servo’s rate limit. The check is implemented separately in gap_follow_node, pure_pursuit_node, auto_map_race_node and neural_racer_node, so a bug in one node’s check does not disable the others.
| State | What the node does | Log state in gap_follow |
|---|---|---|
LB held, /joy fresh |
Drives | gap_follow, corner_fallback |
| LB released | Speed 0 | deadman_released |
No /joy received since start |
Speed 0 | waiting_for_joy |
Last /joy older than 0.5 s |
Speed 0 | joy_stale |
enable_deadman to false on the car. The parameter exists so that tests can run a node without a gamepad, and those tests also move the node off the real /drive. Turning it off on the car removes the only check that protects an autonomy run. It is a team policy, not a tuning value.Because every driving node depends on /joy, joy_node starts in bringup, not in the teleop launch. A car whose gamepad is missing or switched to DirectInput mode cannot drive in any mode.
Check 2: ackermann_mux
ackermann_mux receives commands on two topics and passes the winner on as /ackermann_cmd. The configuration is in mux.yamlGitHub.
| Input | Topic | Priority | Timeout |
|---|---|---|---|
| Manual driving | /teleop |
100 | 0.2 s |
| Autonomy | /drive |
10 | 0.2 s |
The higher priority wins while it is publishing. An input that has been silent for longer than its timeout drops out. joy_teleop publishes on /teleop continuously while teleop_launch.py runs, even with LB released, so /teleop never times out. While teleop runs, no command on /drive reaches the motor.
What protects you in each mode
| Mode | LB deadman | Mux override |
|---|---|---|
Manual (teleop_launch.py) |
joy_teleop only publishes stick values while LB is held |
Not needed: manual is the only source |
| Autonomy (any autonomy node) | Active in the node | Inactive: teleop is not running |
During autonomy, releasing LB is the only software stop. Keep a second person at the battery switch whenever the car drives itself.
What stops the car when something fails
Each failure is caught by a specific mechanism, and some by nothing in software. Knowing which is which is the reason for the person at the battery switch.
| Failure | What stops the car | How fast |
|---|---|---|
| The driver releases LB | The driving node publishes speed 0 | The next tick, 25 ms |
The gamepad switches off, or joy_node crashes |
Each driving node sees /joy go stale and publishes speed 0 |
0.5 s |
| The LiDAR stops | pure_pursuit stops (lidar_scan_stale). gap_follow, driven by scans, stops publishing, and the VESC’s own command timeout stops the motor. |
0.5 s, plus the VESC timeout for gap_follow |
| Localization freezes while the car moves | pure_pursuit’s frozen-pose watchdog |
0.5 s |
| Odometry stops | gap_follow publishes speed 0 (odometry_stale), even with LB held. With fused speed on, either /odom or /odom/fused going stale stops it. |
0.5 s |
The car hits something, on car 1 with imu_impact_stop:=true |
gap_follow latches speed 0 until LB is released and pressed again. An IMU failure never clears the latch. Off by default; the threshold is provisional. |
The next scan after the spike |
| The IMU fails or goes stale | Each gap_follow IMU feature falls back to its off behaviour on the same tick. The car keeps driving. |
The next tick |
| A driving node raises an error | It publishes speed 0, then exits | Immediately |
| A driving node hangs or is killed | Nothing in ROS: the mux forwards nothing and sends no zero. The VESC’s command timeout stops the motor. | The VESC timeout, which is not recorded |
| The Jetson loses power or the USB cable comes out | The VESC’s command timeout | The VESC timeout |
| The car is driving wrongly but every check passes | The person holding LB lets go, or the person at the battery switch | Human reaction time |
The VESC’s command timeout is set in VESC Tool, on the VESC itself. It is the last line of defence for every row that ends in it, and its value has not yet been read off either car.
Rules for driving code
These rules apply to any node that publishes on /drive, /ackermann_cmd, /commands/motor/* or /commands/servo/*.
- Implement the LB check in the node itself, copied from
gap_follow_node’sjoy_callbackand_deadman_statusingap_follow_node.pyGitHub. Another node’s check does not cover yours. - Test all four rows of the table above, asserting the published command, not an internal flag.
- Run new code in this order, without skipping a step: with no driver stack running, then with the wheels off the ground, then on the floor at low speed in open space.
The full contract for a driving node is in Write a driving node.
Limitations
- The deadman checks that the gamepad reports LB held. It cannot tell whether the person holding it is watching the car.
ackermann_muxonly forwards the commands it receives. When every input goes quiet, it forwards nothing and publishes no stop of its own, so the motor then stops only through the VESC firmware’s command timeout, which is set in VESC Tool and cannot be read from ROS. This is whygap_followandpure_pursuitpublish an explicit zero before they exit after an error.
Checked against
car 1 commit 8a88dcc
and
car 2 commit 80da6f5, before the two workspaces merged.