Skip to content

Commit f72f813

Browse files
Add set_odometry service - omni wheel drive controller (ros-controls#2148)
Co-authored-by: Christoph Fröhlich <christophfroehlich@users.noreply.github.com>
1 parent 3cb8969 commit f72f813

File tree

10 files changed

+165
-29
lines changed

10 files changed

+165
-29
lines changed

doc/release_notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ omni_wheel_drive_controller
3535
*****************************
3636
* Parameter ``tf_frame_prefix_enable`` got deprecated and will be removed in a future release (`#2073 <https://github.com/ros-controls/ros2_controllers/pull/2073>`_).
3737
* Now any tilde ("~") character in ``tf_frame_prefix`` is substituted with node namespace. (`#2073 <https://github.com/ros-controls/ros2_controllers/pull/2073>`_).
38+
* Set odometry service added to be used at runtime. (`#2148 <https://github.com/ros-controls/ros2_controllers/pull/2148>`_).
3839

3940
joint_trajectory_controller
4041
***************************

omni_wheel_drive_controller/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export_windows_symbols()
88
# Find dependencies
99
set(THIS_PACKAGE_INCLUDE_DEPENDS
1010
ament_cmake
11+
control_msgs
1112
controller_interface
1213
generate_parameter_library
1314
geometry_msgs
@@ -52,6 +53,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC
5253
${geometry_msgs_TARGETS}
5354
${nav_msgs_TARGETS}
5455
${tf2_msgs_TARGETS}
56+
${control_msgs_TARGETS}
5557
)
5658
pluginlib_export_plugin_description_file(controller_interface omni_wheel_drive_plugin.xml)
5759

omni_wheel_drive_controller/doc/userdoc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Publishers
6464
/tf [tf2_msgs::msg::TFMessage]
6565
tf tree. Published only if ``enable_odom_tf=true``
6666

67+
Services
68+
,,,,,,,,,,,
69+
~/set_odometry [control_msgs::srv::SetOdometry]
70+
This service can be used to set the current odometry of the robot to desired values.
6771

6872
Parameters
6973
,,,,,,,,,,

omni_wheel_drive_controller/include/omni_wheel_drive_controller/odometry.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class Odometry
3232
bool updateOpenLoop(
3333
const double & linear_x_vel, const double & linear_y_vel, const double & angular_vel,
3434
const rclcpp::Time & time);
35-
void resetOdometry();
35+
[[deprecated("Use setOdometry(0.0, 0.0, 0.0) instead")]] void resetOdometry();
36+
void setOdometry(const double & x, const double & y, const double & heading);
3637

3738
double getX() const { return x_; }
3839
double getY() const { return y_; }

omni_wheel_drive_controller/include/omni_wheel_drive_controller/omni_wheel_drive_controller.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020
#include <vector>
2121

22+
#include "control_msgs/srv/set_odometry.hpp"
2223
#include "controller_interface/chainable_controller_interface.hpp"
2324
#include "geometry_msgs/msg/twist_stamped.hpp"
2425
#include "nav_msgs/msg/odometry.hpp"
@@ -66,6 +67,11 @@ class OmniWheelDriveController : public controller_interface::ChainableControlle
6667
controller_interface::CallbackReturn on_error(
6768
const rclcpp_lifecycle::State & previous_state) override;
6869

70+
void set_odometry(
71+
const std::shared_ptr<rmw_request_id_t> request_header,
72+
const std::shared_ptr<control_msgs::srv::SetOdometry::Request> req,
73+
std::shared_ptr<control_msgs::srv::SetOdometry::Response> res);
74+
6975
protected:
7076
std::vector<hardware_interface::CommandInterface> on_export_reference_interfaces() override;
7177

@@ -112,6 +118,11 @@ class OmniWheelDriveController : public controller_interface::ChainableControlle
112118
bool reset();
113119
bool on_set_chained_mode(bool chained_mode) override;
114120

121+
rclcpp::Service<control_msgs::srv::SetOdometry>::SharedPtr set_odom_service_;
122+
std::atomic<bool> set_odom_requested_{false};
123+
realtime_tools::RealtimeThreadSafeBox<control_msgs::srv::SetOdometry::Request>
124+
requested_odom_params_;
125+
115126
private:
116127
void reset_buffers();
117128
};

omni_wheel_drive_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
<build_depend>ros2_control_cmake</build_depend>
2121

22+
<depend>control_msgs</depend>
2223
<depend>controller_interface</depend>
2324
<depend>eigen</depend>
2425
<depend>generate_parameter_library</depend>

omni_wheel_drive_controller/src/odometry.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ void Odometry::resetOdometry()
128128
y_ = 0.0;
129129
heading_ = 0.0;
130130
}
131+
void Odometry::setOdometry(const double & x, const double & y, const double & heading)
132+
{
133+
x_ = x;
134+
y_ = y;
135+
heading_ = heading;
136+
}
131137

132138
void Odometry::setParams(
133139
const double & robot_radius, const double & wheel_radius, const double & wheel_offset,

omni_wheel_drive_controller/src/omni_wheel_drive_controller.cpp

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace
3333
constexpr auto DEFAULT_COMMAND_IN_TOPIC = "~/cmd_vel";
3434
constexpr auto DEFAULT_ODOMETRY_TOPIC = "~/odom";
3535
constexpr auto DEFAULT_TRANSFORM_TOPIC = "/tf";
36+
constexpr auto DEFAULT_SET_ODOM_SERVICE = "~/set_odometry";
3637
} // namespace
3738

3839
namespace omni_wheel_drive_controller
@@ -206,6 +207,12 @@ controller_interface::CallbackReturn OmniWheelDriveController::on_configure(
206207
transform_.header.frame_id = odom_frame_id;
207208
transform_.child_frame_id = base_frame_id;
208209

210+
set_odom_service_ = get_node()->create_service<control_msgs::srv::SetOdometry>(
211+
DEFAULT_SET_ODOM_SERVICE,
212+
std::bind(
213+
&OmniWheelDriveController::set_odometry, this, std::placeholders::_1, std::placeholders::_2,
214+
std::placeholders::_3));
215+
209216
return controller_interface::CallbackReturn::SUCCESS;
210217
}
211218

@@ -317,43 +324,59 @@ controller_interface::return_type OmniWheelDriveController::update_and_write_com
317324
return controller_interface::return_type::OK;
318325
}
319326

320-
// Update odometry
321327
bool odometry_updated = false;
322-
if (params_.open_loop)
328+
329+
// check if odometry set or reset was requested by non-RT thread
330+
if (set_odom_requested_.load())
323331
{
324-
odometry_updated = odometry_.updateOpenLoop(
325-
reference_interfaces_[0], reference_interfaces_[1], reference_interfaces_[2], time);
332+
auto param_op = requested_odom_params_.try_get();
333+
if (param_op.has_value())
334+
{
335+
auto params = param_op.value();
336+
odometry_.setOdometry(params.x, params.y, params.yaw);
337+
odometry_updated = true;
338+
set_odom_requested_.store(false);
339+
}
326340
}
327341
else
328342
{
329-
std::vector<double> wheels_feedback(registered_wheel_handles_.size()); // [rads]
330-
for (size_t i = 0; i < static_cast<size_t>(registered_wheel_handles_.size()); ++i)
343+
// Update odometry
344+
if (params_.open_loop)
345+
{
346+
odometry_updated = odometry_.updateOpenLoop(
347+
reference_interfaces_[0], reference_interfaces_[1], reference_interfaces_[2], time);
348+
}
349+
else
331350
{
332-
// Get wheel feedback
333-
const std::optional<double> wheel_feedback_op =
334-
registered_wheel_handles_[i].feedback.value().get().get_optional();
335-
if (!wheel_feedback_op.has_value())
351+
std::vector<double> wheels_feedback(registered_wheel_handles_.size()); // [rads]
352+
for (size_t i = 0; i < static_cast<size_t>(registered_wheel_handles_.size()); ++i)
336353
{
337-
RCLCPP_DEBUG(logger, "Unable to retrieve data from [%zu] wheel feedback!", i);
338-
return controller_interface::return_type::OK;
339-
}
340-
const double wheel_feedback = wheel_feedback_op.value();
354+
// Get wheel feedback
355+
const std::optional<double> wheel_feedback_op =
356+
registered_wheel_handles_[i].feedback.value().get().get_optional();
357+
if (!wheel_feedback_op.has_value())
358+
{
359+
RCLCPP_DEBUG(logger, "Unable to retrieve data from [%zu] wheel feedback!", i);
360+
return controller_interface::return_type::OK;
361+
}
362+
const double wheel_feedback = wheel_feedback_op.value();
363+
364+
if (std::isnan(wheel_feedback))
365+
{
366+
RCLCPP_ERROR(logger, "The wheel %s is invalid for index [%zu]", feedback_type(), i);
367+
return controller_interface::return_type::ERROR;
368+
}
341369

342-
if (std::isnan(wheel_feedback))
370+
wheels_feedback[i] = wheel_feedback;
371+
}
372+
if (params_.position_feedback)
343373
{
344-
RCLCPP_ERROR(logger, "The wheel %s is invalid for index [%zu]", feedback_type(), i);
345-
return controller_interface::return_type::ERROR;
374+
odometry_updated = odometry_.updateFromPos(wheels_feedback, time);
375+
}
376+
else
377+
{
378+
odometry_updated = odometry_.updateFromVel(wheels_feedback, time);
346379
}
347-
348-
wheels_feedback[i] = wheel_feedback;
349-
}
350-
if (params_.position_feedback)
351-
{
352-
odometry_updated = odometry_.updateFromPos(wheels_feedback, time);
353-
}
354-
else
355-
{
356-
odometry_updated = odometry_.updateFromVel(wheels_feedback, time);
357380
}
358381
}
359382

@@ -436,9 +459,31 @@ void OmniWheelDriveController::compute_and_set_wheel_velocities()
436459
logger, !set_command_result, "Unable to set the command to one of the command handles!");
437460
}
438461

462+
void OmniWheelDriveController::set_odometry(
463+
const std::shared_ptr<rmw_request_id_t> /*request_header*/,
464+
const std::shared_ptr<control_msgs::srv::SetOdometry::Request> req,
465+
std::shared_ptr<control_msgs::srv::SetOdometry::Response> res)
466+
{
467+
if (get_node()->get_current_state().id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
468+
{
469+
res->success = false;
470+
res->message = "Controller is not active";
471+
return;
472+
}
473+
474+
// put requested odom params into RealtimeThreadSafeBox
475+
requested_odom_params_.set(*req);
476+
477+
// flip the flag for thread-safe odom set in the control loop
478+
set_odom_requested_.store(true);
479+
480+
res->success = true;
481+
res->message = "Odometry set request accepted";
482+
}
483+
439484
bool OmniWheelDriveController::reset()
440485
{
441-
odometry_.resetOdometry();
486+
odometry_.setOdometry(0.0, 0.0, 0.0);
442487

443488
reset_buffers();
444489

omni_wheel_drive_controller/test/test_omni_wheel_drive_controller.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,70 @@ TEST_F(OmniWheelDriveControllerTest, 5_wheel_test)
819819
executor.cancel();
820820
}
821821

822+
TEST_F(OmniWheelDriveControllerTest, odometry_set_service)
823+
{
824+
// 0. Initialize and activate
825+
ASSERT_EQ(InitController(), controller_interface::return_type::OK);
826+
827+
// chained mode needed to write directly to reference interfaces
828+
controller_->set_chained_mode(true);
829+
830+
auto state = controller_->get_node()->configure();
831+
ASSERT_EQ(State::PRIMARY_STATE_INACTIVE, state.id());
832+
assignResourcesPosFeedback();
833+
834+
state = controller_->get_node()->activate();
835+
ASSERT_EQ(State::PRIMARY_STATE_ACTIVE, state.id());
836+
837+
const double dt = 0.01; // 100 Hz
838+
rclcpp::Time test_time(0, 0, RCL_ROS_TIME);
839+
const rclcpp::Duration period = rclcpp::Duration::from_seconds(dt);
840+
841+
// simulate movement
842+
auto move_robot = [&](double vx, double vy, double wz)
843+
{
844+
controller_->reference_interfaces_[0] = vx; // linear x
845+
controller_->reference_interfaces_[1] = vy; // linear y
846+
controller_->reference_interfaces_[2] = wz; // angular z
847+
848+
ASSERT_EQ(controller_->update(test_time, period), controller_interface::return_type::OK);
849+
850+
for (size_t i = 0; i < wheels_pos_states_.size(); ++i)
851+
{
852+
double velocity_command = command_itfs_[i]->get_optional().value();
853+
wheels_pos_states_[i] += velocity_command * dt;
854+
}
855+
test_time += period;
856+
};
857+
858+
// 1. Move the robot forward in X
859+
for (int i = 0; i < 5; ++i) move_robot(1.0, 0.0, 0.0);
860+
ASSERT_GT(controller_->odometry_.getX(), 0.0);
861+
862+
// 2. Call Set Odometry Service
863+
auto set_request = std::make_shared<control_msgs::srv::SetOdometry::Request>();
864+
auto set_response = std::make_shared<control_msgs::srv::SetOdometry::Response>();
865+
set_request->x = 5.0;
866+
set_request->y = -2.0;
867+
set_request->yaw = 1.57079632679; // 90 degrees
868+
controller_->set_odometry(nullptr, set_request, set_response);
869+
EXPECT_TRUE(set_response->success);
870+
871+
controller_->update(test_time, period);
872+
EXPECT_NEAR(controller_->odometry_.getX(), 5.0, 1e-6);
873+
EXPECT_NEAR(controller_->odometry_.getY(), -2.0, 1e-6);
874+
EXPECT_NEAR(controller_->odometry_.getHeading(), 1.57079632679, 1e-5);
875+
876+
// 3. Move again to ensure it still works
877+
double start_y = controller_->odometry_.getY();
878+
for (int i = 0; i < 5; ++i) move_robot(1.0, 0.0, 0.0); // we are facing +Y now
879+
EXPECT_GT(controller_->odometry_.getY(), start_y);
880+
881+
// 4. Cleanup
882+
state = controller_->get_node()->deactivate();
883+
ASSERT_EQ(state.id(), State::PRIMARY_STATE_INACTIVE);
884+
}
885+
822886
int main(int argc, char ** argv)
823887
{
824888
::testing::InitGoogleTest(&argc, argv);

omni_wheel_drive_controller/test/test_omni_wheel_drive_controller.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class TestableOmniWheelDriveController
6262
FRIEND_TEST(OmniWheelDriveControllerTest, 3_wheel_rot_test);
6363
FRIEND_TEST(OmniWheelDriveControllerTest, 4_wheel_rot_test);
6464
FRIEND_TEST(OmniWheelDriveControllerTest, 5_wheel_test);
65+
FRIEND_TEST(OmniWheelDriveControllerTest, odometry_set_service);
6566

6667
/**
6768
* @brief wait_for_twist block until a new twist is received.

0 commit comments

Comments
 (0)