Skip to content

Conversation

@eegeugur
Copy link
Contributor

Basic Info

Info Please fill out this column
Ticket(s) this addresses #5214 #5464 #5465
Primary OS tested on Ubuntu
Robotic platform tested on Custom Simulation
Does this PR contain AI generated software? No
Was this PR description generated by AI software? No

Description of contribution in a few bullet points

  • Do not clamp the sampled control proposals state.cvx/cvy/cwz. These are the gaussian proposals around the current nominal sequence and represent the algorithm’s exploration. Clamping them distorts the proposal distribution (no more gaussian) and biases the costs/weights.

  • Derive state.vx/vy/wz from the unclamped state.cvx/cvy/cwz and apply rate & value limits. Then integrate poses from state.vx/vy/wz. This guarantees that all evaluated trajectories are physically feasible while keeping the proposal distribution intact.

  • The control sequence is the softmax-weighted average of the proposals (an optimal proposal), so it does not have to satisfy acceleration limits. applyControlSequenceConstraints() projects that proposal into the corresponding trajectory (which should be the optimal trajectory). However, at first acceleration limits should be applied to the first element of the control sequence wrt the robot’s current velocity. Otherwise, given command does not respect the acceleration limits and potentially wobble, even though the rest of the trajectory respects the acceleration limits internally.

Description of documentation updates required from your changes

  • None

Description of how this change was tested

Future work that may be required in bullet points

For Maintainers:

  • Check that any new parameters added are updated in docs.nav2.org
  • Check that any significant change is added to the migration guide
  • Check that any new features OR changes to existing behaviors are reflected in the tuning guide
  • Check that any new functions have Doxygen added
  • Check that any new features have test coverage
  • Check that any new plugins is added to the plugins page
  • If BT Node, Additionally: add to BT's XML index of nodes for groot, BT package's readme table, and BT library lists
  • Should this be backported to current distributions? If so, tag with backport-*.

@chanhhoang99
Copy link
Contributor

Thanks for the PR, i tested it on kilted with below config

    FollowPath:
      plugin: "nav2_mppi_controller::MPPIController"
      time_steps: 80
      model_dt: 0.05
      batch_size: 1000
      ax_max: 0.5
      ax_min: -0.5
      ay_max: 0.0
      ay_min: 0.0
      az_max: 0.5
      vx_std: 0.3 # increase to reach 1m/s -> 0.35
      vy_std: 0.0
      wz_std: 0.4
      vx_max: 0.5 # increase to reach 1m/s -> 0.1
      vx_min: 0.0
      vy_max: 0.0
      wz_max: 0.4
      iteration_count: 2
      prune_distance: 4.4
      transform_tolerance: 0.1
      temperature: 0.3
      gamma: 0.015
      motion_model: "DiffDrive"
      publish_critics_stats: true
      visualize: true
      publish_optimal_trajectory: true
      regenerate_noises: true
      TrajectoryVisualizer:
        trajectory_step: 20
        time_step: 5
      AckermannConstraints:
        min_turning_r: 0.2
      critics:
        [
          "ConstraintCritic",
          "CostCritic",
          "GoalCritic",
          "GoalAngleCritic",
          "PathAlignCritic",
          "PathFollowCritic",
          "PathAngleCritic",
          "PreferForwardCritic",
        ]
      ConstraintCritic:
        enabled: true
        cost_power: 1
        cost_weight: 4.0
      GoalCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        threshold_to_consider: 0.1
      GoalAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.0
        threshold_to_consider: 0.1
      PreferForwardCritic:
        enabled: true
        cost_power: 1
        cost_weight: 2.0
        threshold_to_consider: 0.1
      CostCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.81
        near_collision_cost: 253
        critical_cost: 300.0
        consider_footprint: true
        collision_cost: 1000000.0
        near_goal_distance: 0.3
        trajectory_point_step: 2
      PathAlignCritic:
        enabled: true
        cost_power: 1
        cost_weight: 14.0
        max_path_occupancy_ratio: 1.0
        trajectory_point_step: 4
        threshold_to_consider: 0.1
        offset_from_furthest: 20
        use_path_orientations: false
      PathFollowCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        offset_from_furthest: 5 # increase to reach 1m/s -> 20
        threshold_to_consider: 0.1
      PathAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 2.0
        offset_from_furthest: 4
        threshold_to_consider: 0.1
        max_angle_to_furthest: 0.2
        mode: 0
Screenshot from 2025-10-16 09-48-31

The below plot especially the angular vel is kind of spiking alot.(This could be because my motor/odom is poor)
And seems like the accel constrain of the angular vel is not repected
image
From the image above the first point of cmd_vel_nav angular vel should be 0.025 or less (my 20Hz velocity smoother angular accel constrain output the correct 0.025 value which is the blue line). Because my model_dt is 0.05, my accel constrain for angular vel is 0.5 so each velocity step should be less than 0.025 (model_dt*accel_constrain.az)

@eegeugur
Copy link
Contributor Author

Thanks for the test. I'll look into it. Just to be sure, the first point of cmd_vel_nav angular vel should be odom twist angular.z +- 0.025, does not have to be in the range of +- 0.025. I guess you were positive the given odom twist angular z to the controller is also 0.0.

@chanhhoang99
Copy link
Contributor

Thanks for the test. I'll look into it. Just to be sure, the first point of cmd_vel_nav angular vel should be odom twist angular.z +- 0.025, does not have to be in the range of +- 0.025. I guess you were positive the given odom twist angular z to the controller is also 0.0.

Yes, you are right, my initial odom value for angular twist velocity is 0.0, I expect the next velocity, it should stay below 0.025.

@SteveMacenski
Copy link
Member

SteveMacenski commented Oct 16, 2025

@chanhhoang99 thanks for the review, I was going to pull you in to test so thanks for beating me to it! @adivardi can you as well since you were involved in that previous work that was reverted?

Let me know if the updated changes fixes your issue. Not much of a purpose of me reviewing the PR for code quality until it meets the need 😉

@eegeugur
Copy link
Contributor Author

eegeugur commented Oct 17, 2025

Okay there were two problems.

First one is if the shift_control_sequence == true, then the optimizer returns next time step's command, not the current. This made the time interval 2dt, which violates the acceleration limits.

The second problem was the SG filter. We were applying the SG filter after enforcing the limits, however, the SG filter may violate the value/rate limits and returns unfeasible commands. Moreover, the control history is also build up based on the sequence of unfeasible commands. I tested this by while constant odom.twist.twist.linear.x = 0.0 & odom.twist.twist.angular.z = 0.0 , the commanded velocities are disturbed after the SG filter.

The following is the difference between commanded velocities and odom twists, where the ax_max = 0.5, ax_min = -0.5, az_max = 0,1 and model_dt=0.05. The controller frequency is 20hz so the shift_control_sequence == true. As expected given commands based on the odom obeys the limits.
cmd_twist_diff

The tested drive model is DiffDrive.

It could be great if you can test this version. @chanhhoang99 @adivardi

@adivardi
Copy link
Contributor

adivardi commented Oct 17, 2025

Nice, I have been working on very similar changes myself :)

This PR does a few changes, I will comment on them separately. Sorry for the length...
For reference my platform is 4-wheel-steering big machine, with low acceleration limits. On the vehicle there is also quite a lot of motor delay.


  1. Do not clamp the sampled control proposals state.cvx/cvy/cwz :
    This is the same as Unclamp noise velocity. #5266. It is theoretically correct and it helps with the acceleration limits and the delay. But it causes wobbliness of the optimal command.
    I tested a lot of variations on this change., none helped

This PR does not solve the wobbliness issue. You can see the optimal solution jumping side to side when driving straight here:
pr5615
image
This is when setting az_max high (1.09), it is even worse with a lower value (which is more true)

Your PR code without this particular change:
pr5615_no_unclamp

Screenshot from 2025-10-17 14-39-27

The change that solves both low acceleration limits and delay is this one (or something similar).


  1. Apply saturation limits on state.vx/vy/vz in predict():
    I also tested an exact same change last weel. It makes sense, but for some reason it limits the speed command when driving straight.
    You can see the results with your code (after reverting change 1).

With the change - speed only reaches ~0.54m/s
Screenshot from 2025-10-17 15-05-03

Without the change, speed reaches the max 0.6m/s
Screenshot from 2025-10-17 14-53-34


  1. Constrain u0 in applyControlSequenceConstraints():
    Make sense. I was also experimenting with this. In my case, it was only needed if change 1 is included, and didn't make a noticeable change otherwise. I think it can be merged. (Open question here in the case of the no-feedback change from above, but that's a tangent)

  1. Publish u0 instead of u1: Makes sense, but didn't make a noticeable change in my tests. I think it can be merged.

  1. SG-Filter change: I also found this to be causing acceleration limits violations within the horizon. I think this can be merged, though I think you should just apply the SGF before applyControlSequenceConstraints inside optimize (like this

My params are very similar to the defaults. I only changed the kinematic limits and increase the horizon & prune distance

    FollowPath:
      plugin: "nav2_mppi_controller::MPPIController"
      time_steps: 100
      model_dt: 0.05
      batch_size: 2000
      ax_max: 2.0
      ax_min: -1.7
      ay_max: 0.0
      ay_min: 0.0
      az_max: 1.09
      vx_max: 0.6
      vx_min: 0.0
      vy_max: 0.0
      wz_max: 1.46
      vx_std: 0.2
      vy_std: 0.2
      wz_std: 0.4
      iteration_count: 1
      prune_distance: 10.0
      transform_tolerance: 0.1
      enforce_path_inversion: true
      inversion_xy_tolerance: 0.2
      inversion_yaw_tolerance: 0.4
      temperature: 0.3
      gamma: 0.015
      motion_model: "Ackermann"
      publish_optimal_trajectory: false
      visualize: true
      regenerate_noises: false
      TrajectoryVisualizer:
        trajectory_step: 15
        time_step: 15
      TrajectoryValidator:
        plugin: "mppi::DefaultOptimalTrajectoryValidator"
        collision_lookahead_time: 2.0
        consider_footprint: true
      AckermannConstraints:
        min_turning_r: 1.35
      critics: [
        "ConstraintCritic", "CostCritic", "GoalCritic",
        "GoalAngleCritic", "PathAlignCritic", "PathFollowCritic",
        "PathAngleCritic", "PreferForwardCritic", "VelocityDeadbandCritic"]
      ConstraintCritic:
        enabled: true
        cost_power: 1
        cost_weight: 4.0
      GoalCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        threshold_to_consider: 1.4
      GoalAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.0
        threshold_to_consider: 0.5
      PreferForwardCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        threshold_to_consider: 0.5
      CostCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.81
        consider_footprint: true
        collision_cost: 1000000.0
        critical_cost: 300.0
        near_collision_cost: 253
        near_goal_distance: 1.0
        trajectory_point_step: 2
      PathAlignCritic:
        enabled: true
        cost_power: 1
        cost_weight: 14.0
        max_path_occupancy_ratio: 0.05
        trajectory_point_step: 4
        threshold_to_consider: 0.5
        offset_from_furthest: 20
        use_path_orientations: true
      PathFollowCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        offset_from_furthest: 5
        threshold_to_consider: 1.4
      PathAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 2.0
        offset_from_furthest: 4
        threshold_to_consider: 0.5
        max_angle_to_furthest: 1.0
        mode: 2
      VelocityDeadbandCritic:
        cost_power: 1
        cost_weight: 2.0
        deadband_velocities: [0.05, 0.05, 0.001]

@eegeugur
Copy link
Contributor Author

eegeugur commented Oct 18, 2025

Thanks for the test and sharing your insights.

Let me comment them.

This PR does not solve the wobbliness issue. You can see the optimal solution jumping side to side when driving straight here

This pr contains several parts that may highly effect the trajectory scoring, and post processing of the commands. A configuration tuned for previous logic, may not be proper for this version. So the unstable/oscillatory movements are expected without tuning. I am expecting this PR to solve the oscillations observed at the first pr of the #5266, since at that pr it was guaranteed the commanded velocities were not obeying the acceleration limits. Obviously this pr cannot prevent oscillations due to other effects (odom erros, poor tuning etc.).

The change that solves both low acceleration limits and delay is this one (or something similar).

This PR is not designed for handling the delayed systems. This mainly fixes problems due to the high asymmetric acceleration limits since its messing up with the costs/weights. Moreover, this PR makes sure the commanded velocity is intended one with the proper limits. #5617 basically violates the acceleration limits. In simpler terms acceleration params became smoothing factors for the given command without considering the actual state of the robot. This pr gives consistent maximum allowed value for constant odom twist values. So actually, this version can give your desired consistent command. If that does not work, it may be

  • low-level controller/drive can’t generate the required torque to generate desired body velocities
  • model_dt is too fine (assuming you can interfere with the system faster than you can)

Instead of #5617 you can increase your acceleration limits/model_dt or both. (Note that you are already accept to violate acceleration limits with #5617 at least you can make the divergence bounded by increasing your acceleration limits)

  1. Apply saturation limits on state.vx/vy/vz in predict():
    I also tested an exact same change last weel. It makes sense, but for some reason it limits the speed command when driving straight.

This is hard to comment without observing the odom plots. I'm not seeing this behavior in the simulation.

  1. Constrain u0 in applyControlSequenceConstraints():
    Make sense. I was also experimenting with this. In my case, it was only needed if change 1 is included, and didn't make a noticeable change otherwise. I think it can be merged. (Open question here in the case of the no-feedback change from above, but that's a tangent)

Yes this change mainly required if the proposals are not clamped.

  1. Publish u0 instead of u1: Makes sense, but didn't make a noticeable change in my tests. I think it can be merged.

It basically doubles the acceleration limits.

  1. SG-Filter change: I also found this to be causing acceleration limits violations within the horizon. I think this can be merged, though I think you should just apply the SGF before applyControlSequenceConstraints inside optimize (like this

The proposed change makes the command history inconsistent with the actual commands. Moreover, I think it is better to make SG filter pure helper and stateless.

@adivardi
Copy link
Contributor

This PR does not solve the wobbliness issue. You can see the optimal solution jumping side to side when driving straight here

This pr contains several parts that may highly effect the trajectory scoring, and post processing of the commands. A configuration tuned for previous logic, may not be proper for this version. So the unstable/oscillatory movements are expected without tuning. I am expecting this PR to solve the oscillations observed at the first pr of the #5266, since at that pr it was guaranteed the commanded velocities were not obeying the acceleration limits. Obviously this pr cannot prevent oscillations due to other effects (odom erros, poor tuning etc.).

But this PR does not solve the oscillations from #5266. You can see the plots and videos I added, which are in simulation with no delay and perfect steering.
Adding this PR causes oscillations in both simulation (with and without delay) and on real robot. Simply reverting the change do not clamp the sampled control proposals removes the oscillations.
For the plots, I already increase az_max to x3 times higher than it's correct value. If I set it low, the oscillations are even worse.
I can increase acceleration constraints a lot more, and that would reduce the oscillations, but will not solve the issue.

If you have a configuration that works, feel free to share it and I will test it.

Instead of #5617 you can increase your acceleration limits/model_dt or both. (Note that you are already accept to violate acceleration limits with #5617 at least you can make the divergence bounded by increasing your acceleration limits)

  • Increase acceleration constraints. I have tried that before.
    • without the change to state.cvx/cwz: reduces the little oscillations, but causes big oscillations as the controller plans infeasible paths, especially with motor delay.
    • with the change: Also reduces the oscillations, but does not solve them.
  • Increase model_dt:
    • I tried to play with model_dt and time_steps together with your PR, and increasing model_dt makes the oscillations even worse.
  1. Apply saturation limits on state.vx/vy/vz in predict():
    I also tested an exact same change last weel. It makes sense, but for some reason it limits the speed command when driving straight.
    This is hard to comment without observing the odom plots. I'm not seeing this behavior in the simulation.

Do you mean the trajectory? It is just a straight line starting from the vehicle.
I observed it multiple times in simulation (with and without delay) and real vehicle (as I said I already tried these sort of changes before)

  1. SG-Filter change: I also found this to be causing acceleration limits violations within the horizon. I think this can be merged, though I think you should just apply the SGF before applyControlSequenceConstraints inside optimize (like this

The proposed change makes the command history inconsistent with the actual commands. Moreover, I think it is better to make SG filter pure helper and stateless.

That's fine then. My only comment is that for multiple iterations you are only applying savitskyGolayFilter once and that even for one iteration applyControlSequenceConstraints is applied twice.

@eegeugur
Copy link
Contributor Author

eegeugur commented Oct 20, 2025

kilted tb4 loopback simulation,

params default except the ax_max = 0.1 insted of 3.0.

    FollowPath:
      plugin: "nav2_mppi_controller::MPPIController"
      time_steps: 56
      model_dt: 0.05
      batch_size: 2000
      ax_max: 0.1
      ax_min: -3.0
      ay_max: 3.0
      ay_min: -3.0
      az_max: 3.5
      vx_std: 0.2
      vy_std: 0.2
      wz_std: 0.4
      vx_max: 0.5
      vx_min: -0.35
      vy_max: 0.5
      wz_max: 1.9
      iteration_count: 1
      prune_distance: 1.7
      transform_tolerance: 0.1
      temperature: 0.3
      gamma: 0.015
      motion_model: "DiffDrive"
      visualize: true
      publish_optimal_trajectory: true
      regenerate_noises: true
      TrajectoryVisualizer:
        trajectory_step: 5
        time_step: 3
      TrajectoryValidator:
        # The validator can be configured with additional parameters if needed.
        plugin: "mppi::DefaultOptimalTrajectoryValidator"
        collision_lookahead_time: 2.0  # Time to look ahead to check for collisions
        consider_footprint: false  # Whether to consider the full robot's footprint (or circle) in trajectory validation collision checks
      AckermannConstraints:
        min_turning_r: 0.2
      critics:
        [
          "ConstraintCritic",
          "CostCritic",
          "GoalCritic",
          "GoalAngleCritic",
          "PathAlignCritic",
          "PathFollowCritic",
          "PathAngleCritic",
          "PreferForwardCritic",
        ]
      ConstraintCritic:
        enabled: true
        cost_power: 1
        cost_weight: 4.0
      GoalCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        threshold_to_consider: 1.4
      GoalAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.0
        threshold_to_consider: 0.5
      PreferForwardCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        threshold_to_consider: 0.5
      CostCritic:
        enabled: true
        cost_power: 1
        cost_weight: 3.81
        near_collision_cost: 253
        critical_cost: 300.0
        consider_footprint: false
        collision_cost: 1000000.0
        near_goal_distance: 1.0
        trajectory_point_step: 2
      PathAlignCritic:
        enabled: true
        cost_power: 1
        cost_weight: 14.0
        max_path_occupancy_ratio: 0.05
        trajectory_point_step: 4
        threshold_to_consider: 0.5
        offset_from_furthest: 20
        use_path_orientations: false
      PathFollowCritic:
        enabled: true
        cost_power: 1
        cost_weight: 5.0
        offset_from_furthest: 5
        threshold_to_consider: 1.4
      PathAngleCritic:
        enabled: true
        cost_power: 1
        cost_weight: 2.0
        offset_from_furthest: 4
        threshold_to_consider: 0.5
        max_angle_to_furthest: 1.0
        mode: 0
      # TwirlingCritic:
      #   enabled: true
      #   twirling_cost_power: 1
      #   twirling_cost_weight: 10.0

without this pr tb4 cannot make any progress,

image

with this pr tb4 can make progress again, however the cmd_vel_nav saturated around 0.25.

image

Increase the PathFollowCritic's weight to 10, and tb4 can reach the max vel again.

image

Without this pr tb4 cannot make progress with highly asymmetric acceleration limits, even-though we increase the PathFollowCritic's weight.

Just changed one weight to obtain the desired vx, also could eliminate the oscillations of wz by adjusting other weights/params.

This pr aims to fix the trajectory scoring and kinematic violations of the current version. It can easily observed with highly asymmetric acceleration limits as mentioned at #5465. It can only resolve the oscillations due to internal inconsistency of the trajectory scoring and kinematic violations, which definitely bring along with #5266.

There may still be points that could be improved further. I noticed that the control history should be cleared after a goal is reached to prevent it from influencing the initial commands for following goals.

@adivardi
Copy link
Contributor

adivardi commented Oct 21, 2025

I tried a bunch more changes today:

  • default params
  • Increasing PathFollowCritic.cost_weight: helps increase the speed, but it never reaches vx_max as before. But even increasing it to 10 already ruins turning (controller cuts the turns to progress on the path). Both xv & wz commands still oscillating.
  • changed az_max, wz_max, vx_std, vz_std
  • reduces PathAlignCritic.cost_weight

All have the same oscillations in the vx & wz commands.

@chanhhoang99
Copy link
Contributor

@adivardi ,
Your latest test is on simulation right ?
I think for this change we should use simulation only since there is no motor response issue.
But to make sure, can you run this script on your simulation to verify there is no odom delay ?

#!/usr/bin/env python3
import math
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from geometry_msgs.msg import TwistStamped
from nav_msgs.msg import Odometry

def clamp(x, lo, hi):
    return max(lo, min(hi, x))

class OneSecondCmdVelFixedDT(Node):
    def __init__(self):
        super().__init__('one_second_cmd_vel_fixed_dt')

        # Parameters
        self.declare_parameter('cmd_vel_topic', '/cmd_vel')
        self.declare_parameter('odom_topic', '/odom')
        self.declare_parameter('dt_s', 0.1)        # <-- fixed period (20 Hz)
        self.declare_parameter('duration_s', 1.0)   # 1 second total
        self.declare_parameter('v_max', 1.0)        # m/s
        self.declare_parameter('a_max', 0.5)        # m/s^2
        self.declare_parameter('forward_only', True)

        self.cmd_vel_topic = self.get_parameter('cmd_vel_topic').value
        self.odom_topic    = self.get_parameter('odom_topic').value
        self.dt            = float(self.get_parameter('dt_s').value)
        self.duration_s    = float(self.get_parameter('duration_s').value)
        self.v_max         = float(self.get_parameter('v_max').value)
        self.a_max         = float(self.get_parameter('a_max').value)
        self.forward_only  = bool(self.get_parameter('forward_only').value)

        # Derived
        self.steps_total = max(1, int(round(self.duration_s / self.dt)))
        self.steps_sent = 0
        self.started = False

        # IO
        self.pub = self.create_publisher(TwistStamped, self.cmd_vel_topic, 10)
        self.sub = self.create_subscription(Odometry, self.odom_topic, self._odom_cb,
                                            qos_profile_sensor_data)

        # State from odom
        self.v_meas = 0.0
        self.have_odom = False

        # Timer runs strictly at dt
        self.timer = self.create_timer(self.dt, self._tick)

        self.get_logger().info(
            f"Will publish to {self.cmd_vel_topic} at {1.0/self.dt:.1f} Hz "
            f"for {self.duration_s:.2f}s (steps={self.steps_total}), "
            f"v_max={self.v_max} m/s, a_max={self.a_max} m/s², based on {self.odom_topic}"
        )

    # Use longitudinal velocity from odom (base_link frame)
    def _odom_cb(self, msg: Odometry):
        self.v_meas = float(msg.twist.twist.linear.x)
        self.have_odom = True

    def _tick(self):
        if not self.have_odom:
            self.get_logger().warn_throttle(2.0, f"Waiting for odom on {self.odom_topic}...")
            return

        # Start counting steps only after we have odom
        if not self.started:
            self.started = True
            self.steps_sent = 0

        if self.steps_sent >= self.steps_total:
            # Done: send stop and exit
            self.pub.publish(TwistStamped())
            self.get_logger().info("Finished 1.0 s at 20 Hz. Stopping and shutting down.")
            rclpy.shutdown()
            return

        # Target is forward v_max (you can change this to any target profile)
        v_target = self.v_max
        if self.forward_only:
            # Don’t command reverse
            v_target = max(0.0, v_target)

        # Compute one-step change limited by ±a_max*dt, based on *measured* speed
        dv_allowed = self.a_max * self.dt
        delta_to_target = v_target - self.v_meas
        step_delta = clamp(delta_to_target, -dv_allowed, dv_allowed)

        v_cmd = self.v_meas + step_delta

        if self.forward_only:
            v_cmd = max(0.0, v_cmd)  # keep forward-only if requested
        v_cmd = clamp(v_cmd, -self.v_max, self.v_max)
        self.get_logger().info(f"v_cmd {v_cmd}")
        # Publish command
        msg = TwistStamped()
        msg.twist.linear.x = v_cmd
        msg.twist.angular.z = 0.0
        self.pub.publish(msg)

        self.steps_sent += 1

def main():
    rclpy.init()
    node = OneSecondCmdVelFixedDT()
    try:
        rclpy.spin(node)
    finally:
        if rclpy.ok():
            rclpy.shutdown()

if __name__ == '__main__':
    main()

This is a small script that simulate cmd_vel sent by MPPI.
Maybe you can modify accel, dt to check. For example if dt is 0.05, accel is 0.5, would it reach a desired velocity after the script end.

@adivardi
Copy link
Contributor

All of the results I posted here are in simulation with no additional delay. But I have confirmed similar results on our robot.
I tried your script, and it reaches 0.47 out of 0.5, so I think that the already the limit from gazebo, ros and the nodes downstream from MPPI.

I have played again with a bunch of params, but the results are the same. Only improvement is when setting high az_max reduces the oscillations, but doesn't remove them.

@chanhhoang99
Copy link
Contributor

0.47/0.5 in 1 second with dt is 0.05 and a max is 0.5 right ?

@adivardi
Copy link
Contributor

0.47/0.5 in 1 second with dt is 0.05 and a max is 0.5 right ?

yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants