Detecting execution collapse before hard E-stop: ros2_kinematic_guard for ROS 2 AMR/AGV

Hi ROS community,
I’ve been working on a small ROS 2 package called ros2_kinematic_guard. It is a pre-E-stop guard for ROS 2 AMR/AGV systems.The idea is simple: Most mobile robot stacks already have timeouts. If /cmd_vel stops arriving, the base driver or controller can stop the robot.But timeouts only answer one question:

Did a command arrive recently?

They do not answer:

Is the robot still moving according to the command it was just given?

That gap matters in AMR/AGV deployments.

Why pre-E-stop detection matters

Safety-rated E-stop systems, safety PLCs, and safety lidars are the final protection layer. ros2_kinematic_guard does not replace them. The goal is different:

Detect execution collapse earlier, before the certified safety layer is forced to intervene.
Examples:

  • wheel slip on wet or oily floors
  • wheel-speed / odometry mismatch
  • localization jumps from lidar / SLAM glitches
  • stale or replayed command windows
  • bad Wi-Fi / 5G command bursts
  • robot shaking, spinning, or over-correcting before safety lidar cuts power

Frequent hard stops may contribute to:

  • manual recovery time
  • production interruption
  • payload instability
  • mechanical stress on wheels, reducers, and brakes
  • unclear root cause during post-incident debugging

Why not just a timeout?

A timeout can detect silence. It cannot detect a physically inconsistent command-feedback episode. For example:

/cmd_vel is still arriving normallybut /odom no longer matches the commanded motion

That can happen during wheel slip, localization jumps, or command bursts after network buffering. ros2_kinematic_guard watches both /cmd_vel and /odom over a short sliding window. When the robot’s measured motion no longer matches the command stream, it emits a compact KinematicStatus JSON and can optionally clamp or brake the outgoing command.
The goal is to provide a local pre-E-stop diagnostic layer. By detecting and self-correcting minor kinematic inconsistencies early, we can significantly reduce the frequency of unforced hard E-stops, thereby improving operational uptime.

Zero-code modification

The package works as an inline ROS 2 topic filter. You do not need to modify Nav2, behavior trees, planners, controllers, or proprietary base drivers.

Nav2 / teleop / planner
        ↓
      /cmd_vel
        ↓
Kinematic Guard
        ↓
  /safe_cmd_vel
        ↓
base driver

It supports three modes:

  • mode:=observe — passive monitoring only, no control intervention
  • mode:=passthrough — wiring test, /safe_cmd_vel equals /cmd_vel
  • mode:=guard — active mode, can clamp velocity or enter BRAKE_AND_RESYNC

This means first-day deployment can be completely passive.

Example output

Healthy window:

{
  "status": "GREEN",
  "residual": 0.0009,
  "causalAlignment": "ALIGNED",
  "dominantCause": "NONE",
  "guardAction": "OBSERVE_ONLY",
  "safeCmd": {
    "linear_vx": 0.8,
    "angular_wz": 0.0
  }
}

Wheel-slip window in observe mode:

{
  "status": "RESYNCING",
  "causalAlignment": "BROKEN",
  "dominantCause": "WHEEL_SLIP",
  "guardAction": "OBSERVE_ONLY",
  "mode": "observe",
  "controlInterceptionEnabled": false
}

Wheel-slip window in guard mode:

{
  "status": "RESYNCING",
  "causalAlignment": "BROKEN",
  "dominantCause": "WHEEL_SLIP",
  "guardAction": "BRAKE_AND_RESYNC",
  "mode": "guard",
  "controlInterceptionEnabled": true,
  "safeCmd": {
    "linear_vx": 0.0,
    "angular_wz": 0.0
  }
}

5-minute demo

The demo does not require Gazebo, Isaac Sim, or a real robot. It runs a lightweight mock AMR/AGV and injects wheel slip:

/cmd_vel
   ↓
Kinematic Guard
   ↓
/safe_cmd_vel
   ↓
Mock Robot
   ↓
/odom
   ↑
Kinematic Guard

Build:

source /opt/ros/humble/setup.bash
colcon build --symlink-install
source install/setup.bash

Run observe mode:

ros2 launch ros2_kinematic_guard start_pre_estop_demo.launch.py \
  profile:=wheel_slip \
  mode:=observe \
  slip_start_sec:=10.0 \
  slip_duration_sec:=12.0

Publish a smooth command:

ros2 topic pub -r 20 /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.8}, angular: {z: 0.0}}"

Watch the guard:

watch -n 0.2 'ros2 topic echo /kinematic_guard/status --field data --once --full-length | awk "/^---$/{exit} {print}" | python3 -m json.tool'

The README also includes a persistent fault debug mode with slip_duration_sec:=9999.0.

Repository:

https://github.com/ZC502/ros2_kinematic_guard.git

I would be very interested in feedback from people working with AMRs/AGVs, Nav2 deployments, base drivers, or post-incident debugging. The core question is:

Would a lightweight command/odom sanity layer be useful before the system escalates to a hard E-stop?

1 Like

Small deployment note based on feedback from sensor-fusion developers:

ros2_kinematic_guard does not need to consume raw encoder odometry. In real AMR/AGV deployments, it should usually subscribe to the best available odometry source, e.g. /odometry/filtered, /fusion/odom, or another fused state estimate.

That keeps the layering clean:

Sensor fusion answers:
  What is the robot actually doing?

Kinematic Guard answers:
  Does the command stream still match that measured motion?

So the recommended deployment is:

/cmd_vel + fused odom → Kinematic Guard → /safe_cmd_vel + KinematicStatus

I added a README note for this. Cleaner odometry should reduce false positives and make the guard decision more deterministic.