Skip to content

Conversation

nitin2606
Copy link
Contributor

Description
This PR introduces the swerve_drive_controller, a new controller for swerve drive robots with four independently steerable wheels, enabling omnidirectional motion in ROS 2. It complements controllers like diff_drive_controller by supporting advanced mobile robot platforms.

Features

Supports geometry_msgs/msg/Twist or TwistStamped velocity inputs (x, y linear; z angular).
Publishes raw nav_msgs/msg/Odometry for user-defined post-processing.
Publishes /tf transforms (optional, if enable_odom_tf=true).
Configurable via YAML for wheel geometry and kinematic constraints.
Changes
Added swerve_drive_controller package (src/, include/, test/).
Exported as a pluginlib plugin (swerve_drive_controller_plugin.xml).
Updated ros2_controllers CMakeLists.txt and package.xml.
Included gtest tests (test/test_swerve_drive_controller.cpp) and config (test/config/test_swerve_drive_controller.yaml).

Copy link
Contributor

@christophfroehlich christophfroehlich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for your contribution. Could you please fix the failing tests? A very quick first review below.

Do you mind add a brief section in the kinematics section of the docs, even in a new PR to get it merged faster? Thanks!

@nitin2606
Copy link
Contributor Author

I'll make changes and push.

@christophfroehlich
Copy link
Contributor

do you mind cherry-picking the changes to the docs in a separate PR? Splitting this up would help to reviewing things.

@nitin2606
Copy link
Contributor Author

do you mind cherry-picking the changes to the docs in a separate PR? Splitting this up would help to reviewing things.

I have raised a separate PR for changes in docs. PR #1712

Copy link
Member

@Maverobot Maverobot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, thanks for your contribution! I've started reviewing the changes and have left a few comments. I will try to find time to work through it and will follow up once I'm done.

Copy link
Contributor

mergify bot commented Jun 24, 2025

This pull request is in conflict. Could you fix it @nitin2606?

@nitin2606
Copy link
Contributor Author

nitin2606 commented Aug 29, 2025

@christophfroehlich, could you kindly review the changes in test_swerve_drive_controller.cpp line no 29. While the code appears to be correct, it is still failing the pre-commit checks. I would also greatly appreciate it if you could review the other changes you suggested.

@Narukara
Copy link
Contributor

Thank you for all your work! I'm trying to test this controller locally with the Gazebo simulator. I'm searching for information on how to compile and use a new controller locally... 😃 If anyone could point me to a suitable tutorial, that would be helpful. I'll also try this myself.

@nitin2606
Copy link
Contributor Author

Thank you for all your work! I'm trying to test this controller locally with the Gazebo simulator. I'm searching for information on how to compile and use a new controller locally... 😃 If anyone could point me to a suitable tutorial, that would be helpful. I'll also try this myself.

I have made a local setup to test this controller, if you want I can share the github link.

@Narukara
Copy link
Contributor

I have made a local setup to test this controller, if you want I can share the github link.

Yes, please! That would be extremely helpful. Thank you for offering to share it. 😄

@nitin2606
Copy link
Contributor Author

I have made a local setup to test this controller, if you want I can share the github link.

Yes, please! That would be extremely helpful. Thank you for offering to share it. 😄

https://github.com/nitin2606/Swerve_Drive.git

{-0.1, 0.175}, // front left
{0.1, 0.175}, // front right
{-0.1, -0.175}, // rear left
{0.1, -0.175} // rear right
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all your work! I really like this work. I note here you use a fixed wheel_positions_, I feel this is unreasonable. I have made similar suggestions before, but I did not receive a response from you. What are your thoughts on this? @nitin2606 .

This is my first time using the review function of GitHub. If there is anything wrong, please correct me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, wheel positions must be taken via parameters. Sorry for the late response.
I'll make necessary changes and push.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed another issue. According to ROS REP 103, the forward direction of a vehicle should align with the X-axis. That means the positions of the four wheels in the coordinate frame should be:

            ^y
  Rear Left |  Front Left
------------+-------------->x
 Rear Right |  Front Right

This is also how the Ackermann vehicle model is designed in this URDF file. However, the current placement here does not seem to comply with this, which could potentially have some implications for the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kinematics code already follows REP-103 (+X forward, +Y left). The issue is just mislabeled wheel positions in the array comments; once reordered or relabeled consistently (FL=+X,+Y; FR=+X,–Y; RR=–X,–Y; RL=–X,+Y), everything aligns correctly.

@Narukara
Copy link
Contributor

There's another issue: the userdoc.rst mentions that the use_stamped_vel option can be used to configure the controller to accept either TwistStamped or Twist messages. However, this does not appear to be implemented in the code — the controller always uses TwistStamped. 🤔

@nitin2606
Copy link
Contributor Author

There's another issue: the userdoc.rst mentions that the use_stamped_vel option can be used to configure the controller to accept either TwistStamped or Twist messages. However, this does not appear to be implemented in the code — the controller always uses TwistStamped. 🤔

userdoc.rst is not updated. All the controllers of ros2_control have switched to TwistStamped only.

computed from wheelbase, trackwidth, offset_x, and offset_y. This
improves flexibility and reduces duplication when changing robot
dimensions.

Signed-off-by: nitin <[email protected]>
Copy link
Contributor

@Narukara Narukara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the right place to handle the wheel radius. I tested the modified controller using gazebo, and both the controls and odometry seem to work fine. By the way, I used the humble version, so I made some necessary changes.

Comment on lines 332 to 334
auto logger = get_node()->get_logger();

RCLCPP_INFO(logger, "Updated Kinematics");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto logger = get_node()->get_logger();
RCLCPP_INFO(logger, "Updated Kinematics");

The controller prints a lot of this when it runs, which is a bit annoying.

wheel_joint_names[i]);
}
axle_handles_[i]->set_position(wheel_command[i].steering_angle);
wheel_handles_[i]->set_velocity(wheel_command[i].drive_velocity);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
wheel_handles_[i]->set_velocity(wheel_command[i].drive_velocity);
wheel_handles_[i]->set_velocity(wheel_command[i].drive_velocity / params_.wheel_radius);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved in latest commit

}
else
{
velocity_array[i] = wheel_handles_[i]->get_feedback();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
velocity_array[i] = wheel_handles_[i]->get_feedback();
velocity_array[i] = wheel_handles_[i]->get_feedback() * params_.wheel_radius;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved in latest commit

@Narukara
Copy link
Contributor

Narukara commented Oct 7, 2025

Another small issue: I found that when I stop sending speed commands, the controller automatically straightens all the wheels. Maybe it would be more reasonable to keep each wheel at its current steering angle?

 - Updated kinematics to compute angular velocity for wheels instead
   of linear velocity for more accurate motion control.
 - Enhanced controller to retain the last steering angle when stopped,
   preventing the wheels from snapping back to zero orientation.
@nitin2606
Copy link
Contributor Author

Another small issue: I found that when I stop sending speed commands, the controller automatically straightens all the wheels. Maybe it would be more reasonable to keep each wheel at its current steering angle?

please check latest commit

Comment on lines 446 to 457
if (realtime_odometry_publisher_)
{
auto & odometry_message = realtime_odometry_publisher_->msg_;
odometry_message.header.stamp = time;
odometry_message.pose.pose.position.x = odometry_.x;
odometry_message.pose.pose.position.y = odometry_.y;
odometry_message.pose.pose.orientation.x = orientation.x();
odometry_message.pose.pose.orientation.y = orientation.y();
odometry_message.pose.pose.orientation.z = orientation.z();
odometry_message.pose.pose.orientation.w = orientation.w();
realtime_odometry_publisher_->tryPublish(odometry_message);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The odometry message published here does not contain twist information (twist.linear.x, twist.linear.y, twist.angular.z). I have confirmed this issue in the Gazebo test.

Copy link
Contributor

@Narukara Narukara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add odometry twist

@nitin2606 nitin2606 requested a review from Narukara October 13, 2025 20:41
@Narukara
Copy link
Contributor

I've found another tricky problem. When I send the command tw.linear.x = -0.5, I expect the car to move backward. But what actually happens is that it first rotates the wheels 180 degrees and then moves forward, as shown in this video.

2025.10.14.15.21.35.webm

@nitin2606
Copy link
Contributor Author

I've found another tricky problem. When I send the command tw.linear.x = -0.5, I expect the car to move backward. But what actually happens is that it first rotates the wheels 180 degrees and then moves forward, as shown in this video.

2025.10.14.15.21.35.webm

This appears to be a kinematics issue — when the robot is commanded to move backward, the wheels shouldn’t rotate; they should simply translate backward.

@christophfroehlich
Copy link
Contributor

I've found another tricky problem. When I send the command tw.linear.x = -0.5, I expect the car to move backward. But what actually happens is that it first rotates the wheels 180 degrees and then moves forward, as shown in this video.
2025.10.14.15.21.35.webm

This appears to be a kinematics issue — when the robot is commanded to move backward, the wheels shouldn’t rotate; they should simply translate backward.

This is a "feature" of the solution of the inverse kinematics not being unique. But this could be handled to have a best effort behavior. Having a look a the video, there should also be some master-slave behavior of the steering and speed command of the wheels be employed, see the reduce_wheel_speed_until_steering_reached parameter here:

https://github.com/ros-controls/ros2_controllers/blob/d7a699fd82acd90a54b8a28f809d29d705968ff6/steering_controllers_library/src/steering_odometry.cpp#L263C21-L284

@nitin2606
Copy link
Contributor Author

I've found another tricky problem. When I send the command tw.linear.x = -0.5, I expect the car to move backward. But what actually happens is that it first rotates the wheels 180 degrees and then moves forward, as shown in this video.
2025.10.14.15.21.35.webm

This appears to be a kinematics issue — when the robot is commanded to move backward, the wheels shouldn’t rotate; they should simply translate backward.

This is a "feature" of the solution of the inverse kinematics not being unique. But this could be handled to have a best effort behavior. Having a look a the video, there should also be some master-slave behavior of the steering and speed command of the wheels be employed, see the reduce_wheel_speed_until_steering_reached parameter here:

https://github.com/ros-controls/ros2_controllers/blob/d7a699fd82acd90a54b8a28f809d29d705968ff6/steering_controllers_library/src/steering_odometry.cpp#L263C21-L284

Please check the latest commit, I have tried to fix the issue

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.

Add a controller for afour wheel independent steering / drive mobile base, aka a swerve drive

7 participants