feat: smooth velocity ramp controller (Issue #350) #372

Merged
sl-jetson merged 1 commits from sl-perception/issue-350-velocity-ramp into main 2026-03-03 16:17:55 -05:00
Collaborator

Summary

Adds a rate-limiting shim between raw /cmd_vel and the drive stack to prevent wheel slip, tipping, and jerky motion from step velocity inputs.

Core library — _velocity_ramp.py (pure Python, no ROS2 deps)

  • VelocityRamp: applies independent accel/decel limits to linear.x and angular.z
  • _ramp_axis(): per-axis limiter — correctly selects decel when magnitude is falling or sign reverses, accel otherwise
  • Emergency stop: step(0.0, 0.0) bypasses ramp → immediate zero (safety override)
  • Asymmetric limits: max_lin_decel may differ from max_lin_accel (e.g. slower accel, faster emergency brake)

ROS2 node — velocity_ramp_node.py

  • Subscribes: /cmd_vel
  • Publishes: /cmd_vel_smooth at configurable rate_hz (default 50 Hz)
  • Parameters: max_lin_accel (0.5 m/s²), max_lin_decel (0.5 m/s²), max_ang_accel (1.0 rad/s²), max_ang_decel (1.0 rad/s²), rate_hz (50)

Tests — test/test_velocity_ramp.py

  • 50/50 tests passing
  • _ramp_axis: all accel/decel/sign-reversal/overshoot cases
  • Construction: invalid params raise, defaults verified, asymmetric decel stored
  • Linear/angular ramp-up: correct step sizes, reaches target, no overshoot
  • Deceleration: asymmetric limits, partial decel (non-zero target avoids e-stop)
  • Emergency stop: immediate zero, state cleared, next ramp from zero
  • Sign reversal: monotone passage through zero, completes
  • Reset: clears state; next step ramps from zero
  • Monotonicity: lin and ang outputs monotone toward target
  • Rate accuracy: 50 Hz → 0.01 m/s/tick at 0.5 m/s²; 100-step convergence verified

Test plan

  • 50/50 unit tests pass
  • On Jetson: ros2 run saltybot_bringup velocity_ramp
  • Verify /cmd_vel_smooth ramps smoothly from teleop step inputs in RViz
  • Confirm e-stop (publishing {0,0}) outputs zero on next /cmd_vel_smooth

Closes #350

🤖 Generated with Claude Code

## Summary Adds a rate-limiting shim between raw `/cmd_vel` and the drive stack to prevent wheel slip, tipping, and jerky motion from step velocity inputs. ### Core library — `_velocity_ramp.py` (pure Python, no ROS2 deps) - **`VelocityRamp`**: applies independent accel/decel limits to `linear.x` and `angular.z` - **`_ramp_axis()`**: per-axis limiter — correctly selects decel when magnitude is falling or sign reverses, accel otherwise - **Emergency stop**: `step(0.0, 0.0)` bypasses ramp → immediate zero (safety override) - **Asymmetric limits**: `max_lin_decel` may differ from `max_lin_accel` (e.g. slower accel, faster emergency brake) ### ROS2 node — `velocity_ramp_node.py` - Subscribes: `/cmd_vel` - Publishes: `/cmd_vel_smooth` at configurable `rate_hz` (default 50 Hz) - Parameters: `max_lin_accel` (0.5 m/s²), `max_lin_decel` (0.5 m/s²), `max_ang_accel` (1.0 rad/s²), `max_ang_decel` (1.0 rad/s²), `rate_hz` (50) ### Tests — `test/test_velocity_ramp.py` - **50/50 tests passing** - `_ramp_axis`: all accel/decel/sign-reversal/overshoot cases - Construction: invalid params raise, defaults verified, asymmetric decel stored - Linear/angular ramp-up: correct step sizes, reaches target, no overshoot - Deceleration: asymmetric limits, partial decel (non-zero target avoids e-stop) - Emergency stop: immediate zero, state cleared, next ramp from zero - Sign reversal: monotone passage through zero, completes - Reset: clears state; next step ramps from zero - Monotonicity: lin and ang outputs monotone toward target - Rate accuracy: 50 Hz → 0.01 m/s/tick at 0.5 m/s²; 100-step convergence verified ## Test plan - [x] 50/50 unit tests pass - [ ] On Jetson: `ros2 run saltybot_bringup velocity_ramp` - [ ] Verify `/cmd_vel_smooth` ramps smoothly from teleop step inputs in RViz - [ ] Confirm e-stop (publishing `{0,0}`) outputs zero on next `/cmd_vel_smooth` Closes #350 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sl-perception added 1 commit 2026-03-03 15:45:29 -05:00
Adds a rate-limiting shim between raw /cmd_vel and the drive stack to
prevent wheel slip, tipping, and jerky motion from step velocity inputs.

Core library — _velocity_ramp.py (pure Python, no ROS2 deps)
- VelocityRamp: applies independent accel/decel limits to linear-x and
  angular-z with configurable max_lin_accel, max_lin_decel,
  max_ang_accel, max_ang_decel
- _ramp_axis(): per-axis rate limiter with correct accel/decel selection
  (decel when |target| < |current| or sign reversal; accel otherwise)
- Emergency stop: step(0.0, 0.0) bypasses ramp → immediate zero output
- Asymmetric limits supported (e.g. faster decel than accel)

ROS2 node — velocity_ramp_node.py
- Subscribes /cmd_vel, publishes /cmd_vel_smooth at configurable rate_hz
- Parameters: max_lin_accel (0.5 m/s²), max_lin_decel (0.5 m/s²),
  max_ang_accel (1.0 rad/s²), max_ang_decel (1.0 rad/s²), rate_hz (50)

Tests — test/test_velocity_ramp.py: 50/50 passing
- _ramp_axis: accel/decel selection, sign reversal, overshoot prevention
- Construction: invalid params raise ValueError, defaults verified
- Linear/angular ramp-up: step size, target reached, no overshoot
- Deceleration: asymmetric limits, partial decel (non-zero target)
- Emergency stop: immediate zero, state cleared, resume from zero
- Sign reversal: passes through zero without jumping
- Reset: state cleared, next ramp starts from zero
- Monotonicity: linear and angular outputs are monotone toward target
- Rate accuracy: 50Hz/10Hz step sizes, 100-step convergence verified

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sl-jetson merged commit 46fc2db8e6 into main 2026-03-03 16:17:55 -05:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: seb/saltylab-firmware#372
No description provided.