Skip to content

Commit cc08f79

Browse files
authored
Add set_odometry service to diff drive controller (#2096)
1 parent fd69e65 commit cc08f79

File tree

9 files changed

+189
-41
lines changed

9 files changed

+189
-41
lines changed

diff_drive_controller/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ set_compiler_options()
66
export_windows_symbols()
77

88
set(THIS_PACKAGE_INCLUDE_DEPENDS
9+
control_msgs
910
control_toolbox
1011
controller_interface
1112
generate_parameter_library
@@ -60,7 +61,8 @@ target_link_libraries(diff_drive_controller
6061
tf2::tf2
6162
${tf2_msgs_TARGETS}
6263
${geometry_msgs_TARGETS}
63-
${nav_msgs_TARGETS})
64+
${nav_msgs_TARGETS}
65+
${control_msgs_TARGETS})
6466
pluginlib_export_plugin_description_file(controller_interface diff_drive_plugin.xml)
6567

6668
if(BUILD_TESTING)

diff_drive_controller/doc/userdoc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Publishers
7070
~/cmd_vel_out [geometry_msgs/msg/TwistStamped]
7171
Velocity command for the controller, where limits were applied. Published only if ``publish_limited_velocity=true``
7272

73+
Services
74+
,,,,,,,,,,,
75+
~/set_odometry [control_msgs::srv::SetOdometry]
76+
This service can be used to set the current odometry of the robot to desired values.
7377

7478
Parameters
7579
,,,,,,,,,,,,

diff_drive_controller/include/diff_drive_controller/diff_drive_controller.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
#ifndef DIFF_DRIVE_CONTROLLER__DIFF_DRIVE_CONTROLLER_HPP_
2020
#define DIFF_DRIVE_CONTROLLER__DIFF_DRIVE_CONTROLLER_HPP_
2121

22+
#include <atomic>
2223
#include <chrono>
2324
#include <memory>
2425
#include <queue>
2526
#include <string>
2627
#include <vector>
2728

29+
#include "control_msgs/srv/set_odometry.hpp"
2830
#include "controller_interface/chainable_controller_interface.hpp"
2931
#include "diff_drive_controller/odometry.hpp"
3032
#include "diff_drive_controller/speed_limiter.hpp"
@@ -76,6 +78,11 @@ class DiffDriveController : public controller_interface::ChainableControllerInte
7678
controller_interface::CallbackReturn on_error(
7779
const rclcpp_lifecycle::State & previous_state) override;
7880

81+
void set_odometry(
82+
const std::shared_ptr<rmw_request_id_t> request_header,
83+
const std::shared_ptr<control_msgs::srv::SetOdometry::Request> req,
84+
std::shared_ptr<control_msgs::srv::SetOdometry::Response> res);
85+
7986
protected:
8087
bool on_set_chained_mode(bool chained_mode) override;
8188

@@ -147,6 +154,11 @@ class DiffDriveController : public controller_interface::ChainableControllerInte
147154
rclcpp::Duration publish_period_ = rclcpp::Duration::from_nanoseconds(0);
148155
rclcpp::Time previous_publish_timestamp_{0, 0, RCL_CLOCK_UNINITIALIZED};
149156

157+
rclcpp::Service<control_msgs::srv::SetOdometry>::SharedPtr set_odom_service_;
158+
std::atomic<bool> set_odom_requested_{false};
159+
realtime_tools::RealtimeThreadSafeBox<control_msgs::srv::SetOdometry::Request>
160+
requested_odom_params_;
161+
150162
bool reset();
151163
void halt();
152164

diff_drive_controller/include/diff_drive_controller/odometry.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ class Odometry
5555
bool update_from_pos(double left_pos, double right_pos, double dt);
5656
bool update_from_vel(double left_vel, double right_vel, double dt);
5757
bool try_update_open_loop(double linear_vel, double angular_vel, double dt);
58-
void resetOdometry();
58+
void setOdometry(double x, double y, double heading);
59+
[[deprecated("Use setOdometry(0.0, 0.0, 0.0) instead")]] void resetOdometry();
5960

6061
double getX() const { return x_; }
6162
double getY() const { return y_; }

diff_drive_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<build_depend>ros2_control_cmake</build_depend>
2727

2828
<depend>backward_ros</depend>
29+
<depend>control_msgs</depend>
2930
<depend>control_toolbox</depend>
3031
<depend>controller_interface</depend>
3132
<depend>geometry_msgs</depend>

diff_drive_controller/src/diff_drive_controller.cpp

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
#include "lifecycle_msgs/msg/state.hpp"
2929
#include "rclcpp/logging.hpp"
3030
#include "tf2/LinearMath/Quaternion.hpp"
31+
#include "tf2/impl/utils.hpp"
3132

3233
namespace
3334
{
3435
constexpr auto DEFAULT_COMMAND_TOPIC = "~/cmd_vel";
3536
constexpr auto DEFAULT_COMMAND_OUT_TOPIC = "~/cmd_vel_out";
3637
constexpr auto DEFAULT_ODOMETRY_TOPIC = "~/odom";
3738
constexpr auto DEFAULT_TRANSFORM_TOPIC = "/tf";
39+
constexpr auto DEFAULT_SET_ODOM_SERVICE = "~/set_odometry";
3840
} // namespace
3941

4042
namespace diff_drive_controller
@@ -161,56 +163,73 @@ controller_interface::return_type DiffDriveController::update_and_write_commands
161163
const double left_wheel_radius = params_.left_wheel_radius_multiplier * params_.wheel_radius;
162164
const double right_wheel_radius = params_.right_wheel_radius_multiplier * params_.wheel_radius;
163165

164-
// Update odometry
165166
bool odometry_updated = false;
166-
if (params_.open_loop)
167+
168+
// check if odometry set or reset was requested by non-RT thread
169+
if (set_odom_requested_.load())
167170
{
168-
odometry_updated =
169-
odometry_.try_update_open_loop(linear_command, angular_command, period.seconds());
171+
auto param_op = requested_odom_params_.try_get();
172+
if (param_op.has_value())
173+
{
174+
auto params = param_op.value();
175+
odometry_.setOdometry(params.x, params.y, params.yaw);
176+
odometry_updated = true;
177+
set_odom_requested_.store(false);
178+
}
170179
}
171180
else
172181
{
173-
double left_feedback_mean = 0.0;
174-
double right_feedback_mean = 0.0;
175-
for (size_t index = 0; index < static_cast<size_t>(wheels_per_side_); ++index)
182+
// Update odometry
183+
if (params_.open_loop)
176184
{
177-
const auto left_feedback_op =
178-
registered_left_wheel_handles_[index].feedback.value().get().get_optional();
179-
const auto right_feedback_op =
180-
registered_right_wheel_handles_[index].feedback.value().get().get_optional();
181-
182-
if (!left_feedback_op.has_value() || !right_feedback_op.has_value())
185+
odometry_updated =
186+
odometry_.try_update_open_loop(linear_command, angular_command, period.seconds());
187+
}
188+
else
189+
{
190+
double left_feedback_mean = 0.0;
191+
double right_feedback_mean = 0.0;
192+
for (size_t index = 0; index < static_cast<size_t>(wheels_per_side_); ++index)
183193
{
184-
RCLCPP_DEBUG(logger, "Unable to retrieve the data from the left or right wheels feedback!");
185-
return controller_interface::return_type::OK;
186-
}
194+
const auto left_feedback_op =
195+
registered_left_wheel_handles_[index].feedback.value().get().get_optional();
196+
const auto right_feedback_op =
197+
registered_right_wheel_handles_[index].feedback.value().get().get_optional();
187198

188-
const double left_feedback = left_feedback_op.value();
189-
const double right_feedback = right_feedback_op.value();
199+
if (!left_feedback_op.has_value() || !right_feedback_op.has_value())
200+
{
201+
RCLCPP_DEBUG(
202+
logger, "Unable to retrieve the data from the left or right wheels feedback!");
203+
return controller_interface::return_type::OK;
204+
}
190205

191-
if (std::isnan(left_feedback) || std::isnan(right_feedback))
192-
{
193-
RCLCPP_ERROR(
194-
logger, "Either the left or right wheel %s is invalid for index [%zu]", feedback_type(),
195-
index);
196-
return controller_interface::return_type::ERROR;
197-
}
206+
const double left_feedback = left_feedback_op.value();
207+
const double right_feedback = right_feedback_op.value();
198208

199-
left_feedback_mean += left_feedback;
200-
right_feedback_mean += right_feedback;
201-
}
202-
left_feedback_mean /= static_cast<double>(wheels_per_side_);
203-
right_feedback_mean /= static_cast<double>(wheels_per_side_);
209+
if (std::isnan(left_feedback) || std::isnan(right_feedback))
210+
{
211+
RCLCPP_ERROR(
212+
logger, "Either the left or right wheel %s is invalid for index [%zu]", feedback_type(),
213+
index);
214+
return controller_interface::return_type::ERROR;
215+
}
216+
217+
left_feedback_mean += left_feedback;
218+
right_feedback_mean += right_feedback;
219+
}
220+
left_feedback_mean /= static_cast<double>(wheels_per_side_);
221+
right_feedback_mean /= static_cast<double>(wheels_per_side_);
204222

205-
if (params_.position_feedback)
206-
{
207-
odometry_updated =
208-
odometry_.update_from_pos(left_feedback_mean, right_feedback_mean, period.seconds());
209-
}
210-
else
211-
{
212-
odometry_updated =
213-
odometry_.update_from_vel(left_feedback_mean, right_feedback_mean, period.seconds());
223+
if (params_.position_feedback)
224+
{
225+
odometry_updated =
226+
odometry_.update_from_pos(left_feedback_mean, right_feedback_mean, period.seconds());
227+
}
228+
else
229+
{
230+
odometry_updated =
231+
odometry_.update_from_vel(left_feedback_mean, right_feedback_mean, period.seconds());
232+
}
214233
}
215234
}
216235

@@ -492,6 +511,11 @@ controller_interface::CallbackReturn DiffDriveController::on_configure(
492511
odometry_transform_message_.transforms.front().header.frame_id = odom_frame_id;
493512
odometry_transform_message_.transforms.front().child_frame_id = base_frame_id;
494513

514+
set_odom_service_ = get_node()->create_service<control_msgs::srv::SetOdometry>(
515+
DEFAULT_SET_ODOM_SERVICE, std::bind(
516+
&DiffDriveController::set_odometry, this, std::placeholders::_1,
517+
std::placeholders::_2, std::placeholders::_3));
518+
495519
previous_update_timestamp_ = get_node()->get_clock()->now();
496520
return controller_interface::CallbackReturn::SUCCESS;
497521
}
@@ -556,6 +580,28 @@ controller_interface::CallbackReturn DiffDriveController::on_error(const rclcpp_
556580
return controller_interface::CallbackReturn::SUCCESS;
557581
}
558582

583+
void DiffDriveController::set_odometry(
584+
const std::shared_ptr<rmw_request_id_t> /*request_header*/,
585+
const std::shared_ptr<control_msgs::srv::SetOdometry::Request> req,
586+
std::shared_ptr<control_msgs::srv::SetOdometry::Response> res)
587+
{
588+
if (get_node()->get_current_state().id() != lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
589+
{
590+
res->success = false;
591+
res->message = "Controller is not active";
592+
return;
593+
}
594+
595+
// put requested odom params into RealtimeThreadSafeBox
596+
requested_odom_params_.set(*req);
597+
598+
// flip the flag for thread-safe odom set in the control loop
599+
set_odom_requested_.store(true);
600+
601+
res->success = true;
602+
res->message = "Odometry set request accepted";
603+
}
604+
559605
bool DiffDriveController::reset()
560606
{
561607
odometry_.resetOdometry();

diff_drive_controller/src/odometry.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,19 @@ bool Odometry::try_update_open_loop(double linear_vel, double angular_vel, doubl
167167
return true;
168168
}
169169

170+
void Odometry::setOdometry(double x, double y, double heading)
171+
{
172+
x_ = x;
173+
y_ = y;
174+
heading_ = heading;
175+
resetAccumulators();
176+
}
170177
void Odometry::resetOdometry()
171178
{
172179
x_ = 0.0;
173180
y_ = 0.0;
174181
heading_ = 0.0;
182+
resetAccumulators();
175183
}
176184

177185
void Odometry::setWheelParams(

diff_drive_controller/test/test_diff_drive_controller.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class TestableDiffDriveController : public diff_drive_controller::DiffDriveContr
4545
{
4646
public:
4747
using DiffDriveController::DiffDriveController;
48+
using DiffDriveController::odometry_;
4849

4950
/**
5051
* @brief wait_for_twist block until a new twist is received.
@@ -1166,6 +1167,78 @@ TEST_F(TestDiffDriveController, command_with_zero_timestamp_is_accepted_with_war
11661167
executor.cancel();
11671168
}
11681169

1170+
TEST_F(TestDiffDriveController, odometry_set_service)
1171+
{
1172+
// 0. Initialize and activate the controller
1173+
ASSERT_EQ(
1174+
InitController(
1175+
left_wheel_names, right_wheel_names,
1176+
{rclcpp::Parameter("wheel_separation", 0.4), rclcpp::Parameter("wheel_radius", 1.0)}),
1177+
controller_interface::return_type::OK);
1178+
1179+
rclcpp::executors::SingleThreadedExecutor executor;
1180+
executor.add_node(controller_->get_node()->get_node_base_interface());
1181+
1182+
auto state = controller_->configure();
1183+
assignResourcesPosFeedback();
1184+
1185+
ASSERT_EQ(State::PRIMARY_STATE_INACTIVE, state.id());
1186+
EXPECT_EQ(0.01, left_wheel_vel_cmd_->get_optional().value());
1187+
EXPECT_EQ(0.02, right_wheel_vel_cmd_->get_optional().value());
1188+
1189+
state = controller_->get_node()->activate();
1190+
ASSERT_EQ(State::PRIMARY_STATE_ACTIVE, state.id());
1191+
1192+
rclcpp::Time test_time(0, 0, RCL_ROS_TIME);
1193+
rclcpp::Duration period = rclcpp::Duration::from_seconds(0.1);
1194+
1195+
// 1. Move the robot first
1196+
publish(1.0, 0.0);
1197+
controller_->wait_for_twist(executor);
1198+
controller_->update(test_time, period);
1199+
test_time += period;
1200+
1201+
// verify initial movement
1202+
ASSERT_GT(controller_->odometry_.getX(), 0.0);
1203+
1204+
// 2. Stop and call odom set service
1205+
publish(0.0, 0.0);
1206+
controller_->wait_for_twist(executor);
1207+
auto set_request = std::make_shared<control_msgs::srv::SetOdometry::Request>();
1208+
auto set_response = std::make_shared<control_msgs::srv::SetOdometry::Response>();
1209+
set_request->x = 5.0;
1210+
set_request->y = -2.0;
1211+
set_request->yaw = 1.57079632679; // 90 degrees
1212+
controller_->set_odometry(nullptr, set_request, set_response);
1213+
EXPECT_TRUE(set_response->success);
1214+
1215+
// run update to process and verify odom values
1216+
controller_->update(test_time, period);
1217+
test_time += period;
1218+
EXPECT_NEAR(controller_->odometry_.getX(), 5.0, 1e-6);
1219+
EXPECT_NEAR(controller_->odometry_.getY(), -2.0, 1e-6);
1220+
EXPECT_NEAR(controller_->odometry_.getHeading(), 1.57079632679, 1e-5); // 90 deg
1221+
1222+
// 3. Move again to ensure it still works
1223+
publish(1.0, 0.0); // we move in Y now
1224+
controller_->wait_for_twist(executor);
1225+
1226+
// simulate the movement by updating the position feedback
1227+
position_values_[0] += 0.1; // left wheel moved
1228+
position_values_[1] += 0.1; // right wheel moved
1229+
controller_->update(test_time, period);
1230+
test_time += period;
1231+
EXPECT_GT(controller_->odometry_.getY(), -2.0);
1232+
1233+
// 4. Deactivate and cleanup
1234+
std::this_thread::sleep_for(std::chrono::milliseconds(300));
1235+
state = controller_->get_node()->deactivate();
1236+
ASSERT_EQ(state.id(), State::PRIMARY_STATE_INACTIVE);
1237+
state = controller_->get_node()->cleanup();
1238+
ASSERT_EQ(state.id(), State::PRIMARY_STATE_UNCONFIGURED);
1239+
executor.cancel();
1240+
}
1241+
11691242
int main(int argc, char ** argv)
11701243
{
11711244
::testing::InitGoogleTest(&argc, argv);

doc/release_notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ diff_drive_controller
1717
*****************************
1818
* Parameter ``tf_frame_prefix_enable`` got deprecated and will be removed in a future release (`#1997 <https://github.com/ros-controls/ros2_controllers/pull/1997>`_).
1919
* Now any tilde ("~") character in ``tf_frame_prefix`` is substituted with node namespace. (`#1997 <https://github.com/ros-controls/ros2_controllers/pull/1997>`_).
20+
* Set odometry service added to be used at runtime. (`#2096 <https://github.com/ros-controls/ros2_controllers/pull/2096>`_).
2021

2122
mecanum_drive_controller
2223
*****************************

0 commit comments

Comments
 (0)