Skip to content

Commit 78ea525

Browse files
authored
Add option to use open-loop control with Rotation Shim (#4880)
* Initial implementation Signed-off-by: RBT22 <rozgonyibalint@gmail.com> * replace feedback param with closed_loop Signed-off-by: RBT22 <rozgonyibalint@gmail.com> * Reset last_angular_vel_ in activate method Signed-off-by: RBT22 <rozgonyibalint@gmail.com> * Add closed_loop parameter to dynamicParametersCallback Signed-off-by: RBT22 <rozgonyibalint@gmail.com> * Add tests Signed-off-by: RBT22 <rozgonyibalint@gmail.com> * Override reset function Signed-off-by: RBT22 <rozgonyibalint@gmail.com> --------- Signed-off-by: RBT22 <rozgonyibalint@gmail.com>
1 parent ade7c96 commit 78ea525

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

nav2_rotation_shim_controller/include/nav2_rotation_shim_controller/nav2_rotation_shim_controller.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <memory>
2121
#include <algorithm>
2222
#include <mutex>
23+
#include <limits>
2324

2425
#include "rclcpp/rclcpp.hpp"
2526
#include "pluginlib/class_loader.hpp"
@@ -102,6 +103,11 @@ class RotationShimController : public nav2_core::Controller
102103
*/
103104
void setSpeedLimit(const double & speed_limit, const bool & percentage) override;
104105

106+
/**
107+
* @brief Reset the state of the controller
108+
*/
109+
void reset() override;
110+
105111
protected:
106112
/**
107113
* @brief Finds the point on the path that is roughly the sampling
@@ -179,6 +185,8 @@ class RotationShimController : public nav2_core::Controller
179185
double rotate_to_heading_angular_vel_, max_angular_accel_;
180186
double control_duration_, simulate_ahead_time_;
181187
bool rotate_to_goal_heading_, in_rotation_, rotate_to_heading_once_;
188+
bool closed_loop_;
189+
double last_angular_vel_ = std::numeric_limits<double>::max();
182190

183191
// Dynamic parameters handler
184192
std::mutex mutex_;

nav2_rotation_shim_controller/src/nav2_rotation_shim_controller.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ void RotationShimController::configure(
7373
node, plugin_name_ + ".rotate_to_goal_heading", rclcpp::ParameterValue(false));
7474
nav2_util::declare_parameter_if_not_declared(
7575
node, plugin_name_ + ".rotate_to_heading_once", rclcpp::ParameterValue(false));
76+
nav2_util::declare_parameter_if_not_declared(
77+
node, plugin_name_ + ".closed_loop", rclcpp::ParameterValue(true));
7678

7779
node->get_parameter(plugin_name_ + ".angular_dist_threshold", angular_dist_threshold_);
7880
node->get_parameter(plugin_name_ + ".angular_disengage_threshold", angular_disengage_threshold_);
@@ -89,6 +91,7 @@ void RotationShimController::configure(
8991

9092
node->get_parameter(plugin_name_ + ".rotate_to_goal_heading", rotate_to_goal_heading_);
9193
node->get_parameter(plugin_name_ + ".rotate_to_heading_once", rotate_to_heading_once_);
94+
node->get_parameter(plugin_name_ + ".closed_loop", closed_loop_);
9295

9396
try {
9497
primary_controller_ = lp_loader_.createUniqueInstance(primary_controller);
@@ -119,6 +122,7 @@ void RotationShimController::activate()
119122

120123
primary_controller_->activate();
121124
in_rotation_ = false;
125+
last_angular_vel_ = std::numeric_limits<double>::max();
122126

123127
auto node = node_.lock();
124128
dyn_params_handler_ = node->add_on_set_parameters_callback(
@@ -184,7 +188,9 @@ geometry_msgs::msg::TwistStamped RotationShimController::computeVelocityCommands
184188

185189
double angular_distance_to_heading = angles::shortest_angular_distance(pose_yaw, goal_yaw);
186190

187-
return computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
191+
auto cmd_vel = computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
192+
last_angular_vel_ = cmd_vel.twist.angular.z;
193+
return cmd_vel;
188194
}
189195
} catch (const std::runtime_error & e) {
190196
RCLCPP_INFO(
@@ -213,7 +219,9 @@ geometry_msgs::msg::TwistStamped RotationShimController::computeVelocityCommands
213219
logger_,
214220
"Robot is not within the new path's rough heading, rotating to heading...");
215221
in_rotation_ = true;
216-
return computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
222+
auto cmd_vel = computeRotateToHeadingCommand(angular_distance_to_heading, pose, velocity);
223+
last_angular_vel_ = cmd_vel.twist.angular.z;
224+
return cmd_vel;
217225
} else {
218226
RCLCPP_DEBUG(
219227
logger_,
@@ -232,7 +240,9 @@ geometry_msgs::msg::TwistStamped RotationShimController::computeVelocityCommands
232240

233241
// If at this point, use the primary controller to path track
234242
in_rotation_ = false;
235-
return primary_controller_->computeVelocityCommands(pose, velocity, goal_checker);
243+
auto cmd_vel = primary_controller_->computeVelocityCommands(pose, velocity, goal_checker);
244+
last_angular_vel_ = cmd_vel.twist.angular.z;
245+
return cmd_vel;
236246
}
237247

238248
geometry_msgs::msg::PoseStamped RotationShimController::getSampledPathPt()
@@ -290,13 +300,18 @@ RotationShimController::computeRotateToHeadingCommand(
290300
const geometry_msgs::msg::PoseStamped & pose,
291301
const geometry_msgs::msg::Twist & velocity)
292302
{
303+
auto current = closed_loop_ ? velocity.angular.z : last_angular_vel_;
304+
if (current == std::numeric_limits<double>::max()) {
305+
current = 0.0;
306+
}
307+
293308
geometry_msgs::msg::TwistStamped cmd_vel;
294309
cmd_vel.header = pose.header;
295310
const double sign = angular_distance_to_heading > 0.0 ? 1.0 : -1.0;
296311
const double angular_vel = sign * rotate_to_heading_angular_vel_;
297312
const double & dt = control_duration_;
298-
const double min_feasible_angular_speed = velocity.angular.z - max_angular_accel_ * dt;
299-
const double max_feasible_angular_speed = velocity.angular.z + max_angular_accel_ * dt;
313+
const double min_feasible_angular_speed = current - max_angular_accel_ * dt;
314+
const double max_feasible_angular_speed = current + max_angular_accel_ * dt;
300315
cmd_vel.twist.angular.z =
301316
std::clamp(angular_vel, min_feasible_angular_speed, max_feasible_angular_speed);
302317

@@ -373,6 +388,12 @@ void RotationShimController::setSpeedLimit(const double & speed_limit, const boo
373388
primary_controller_->setSpeedLimit(speed_limit, percentage);
374389
}
375390

391+
void RotationShimController::reset()
392+
{
393+
last_angular_vel_ = std::numeric_limits<double>::max();
394+
primary_controller_->reset();
395+
}
396+
376397
rcl_interfaces::msg::SetParametersResult
377398
RotationShimController::dynamicParametersCallback(std::vector<rclcpp::Parameter> parameters)
378399
{
@@ -400,6 +421,8 @@ RotationShimController::dynamicParametersCallback(std::vector<rclcpp::Parameter>
400421
rotate_to_goal_heading_ = parameter.as_bool();
401422
} else if (name == plugin_name_ + ".rotate_to_heading_once") {
402423
rotate_to_heading_once_ = parameter.as_bool();
424+
} else if (name == plugin_name_ + ".closed_loop") {
425+
closed_loop_ = parameter.as_bool();
403426
}
404427
}
405428
}

nav2_rotation_shim_controller/test/test_shim_controller.cpp

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,84 @@ TEST(RotationShimControllerTest, computeVelocityTests)
317317
EXPECT_THROW(controller->computeVelocityCommands(pose, velocity, &checker), std::runtime_error);
318318
}
319319

320+
TEST(RotationShimControllerTest, openLoopRotationTests) {
321+
auto ctrl = std::make_shared<RotationShimShim>();
322+
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("ShimControllerTest");
323+
std::string name = "PathFollower";
324+
auto tf = std::make_shared<tf2_ros::Buffer>(node->get_clock());
325+
auto listener = std::make_shared<tf2_ros::TransformListener>(*tf, node, true);
326+
auto costmap = std::make_shared<nav2_costmap_2d::Costmap2DROS>("fake_costmap");
327+
rclcpp_lifecycle::State state;
328+
costmap->on_configure(state);
329+
auto tf_broadcaster = std::make_shared<tf2_ros::TransformBroadcaster>(node);
330+
331+
geometry_msgs::msg::TransformStamped transform;
332+
transform.header.frame_id = "base_link";
333+
transform.child_frame_id = "odom";
334+
transform.transform.rotation.x = 0.0;
335+
transform.transform.rotation.y = 0.0;
336+
transform.transform.rotation.z = 0.0;
337+
transform.transform.rotation.w = 1.0;
338+
tf_broadcaster->sendTransform(transform);
339+
340+
// set a valid primary controller so we can do lifecycle
341+
node->declare_parameter(
342+
"PathFollower.primary_controller",
343+
std::string("nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController"));
344+
node->declare_parameter(
345+
"controller_frequency",
346+
20.0);
347+
node->declare_parameter(
348+
"PathFollower.rotate_to_goal_heading",
349+
true);
350+
node->declare_parameter(
351+
"PathFollower.closed_loop",
352+
false);
353+
354+
auto controller = std::make_shared<RotationShimShim>();
355+
controller->configure(node, name, tf, costmap);
356+
controller->activate();
357+
358+
// Test state update and path setting
359+
nav_msgs::msg::Path path;
360+
path.header.frame_id = "base_link";
361+
path.poses.resize(4);
362+
363+
geometry_msgs::msg::PoseStamped pose;
364+
pose.header.frame_id = "base_link";
365+
geometry_msgs::msg::Twist velocity;
366+
nav2_controller::SimpleGoalChecker checker;
367+
node->declare_parameter(
368+
"checker.xy_goal_tolerance",
369+
1.0);
370+
checker.initialize(node, "checker", costmap);
371+
372+
path.header.frame_id = "base_link";
373+
path.poses[0].pose.position.x = 0.0;
374+
path.poses[0].pose.position.y = 0.0;
375+
path.poses[1].pose.position.x = 0.05;
376+
path.poses[1].pose.position.y = 0.05;
377+
path.poses[2].pose.position.x = 0.10;
378+
path.poses[2].pose.position.y = 0.10;
379+
// goal position within checker xy_goal_tolerance
380+
path.poses[3].pose.position.x = 0.20;
381+
path.poses[3].pose.position.y = 0.20;
382+
// goal heading 45 degrees to the left
383+
path.poses[3].pose.orientation.z = -0.3826834;
384+
path.poses[3].pose.orientation.w = 0.9238795;
385+
path.poses[3].header.frame_id = "base_link";
386+
387+
// Calculate first velocity command
388+
controller->setPlan(path);
389+
auto cmd_vel = controller->computeVelocityCommands(pose, velocity, &checker);
390+
EXPECT_NEAR(cmd_vel.twist.angular.z, -0.16, 1e-4);
391+
392+
// Test second velocity command with wrong odometry
393+
velocity.angular.z = 1.8;
394+
cmd_vel = controller->computeVelocityCommands(pose, velocity, &checker);
395+
EXPECT_NEAR(cmd_vel.twist.angular.z, -0.32, 1e-4);
396+
}
397+
320398
TEST(RotationShimControllerTest, computeVelocityGoalRotationTests) {
321399
auto ctrl = std::make_shared<RotationShimShim>();
322400
auto node = std::make_shared<rclcpp_lifecycle::LifecycleNode>("ShimControllerTest");
@@ -550,7 +628,8 @@ TEST(RotationShimControllerTest, testDynamicParameter)
550628
rclcpp::Parameter("test.simulate_ahead_time", 7.0),
551629
rclcpp::Parameter("test.primary_controller", std::string("HI")),
552630
rclcpp::Parameter("test.rotate_to_goal_heading", true),
553-
rclcpp::Parameter("test.rotate_to_heading_once", true)});
631+
rclcpp::Parameter("test.rotate_to_heading_once", true),
632+
rclcpp::Parameter("test.closed_loop", false)});
554633

555634
rclcpp::spin_until_future_complete(
556635
node->get_node_base_interface(),
@@ -563,4 +642,5 @@ TEST(RotationShimControllerTest, testDynamicParameter)
563642
EXPECT_EQ(node->get_parameter("test.simulate_ahead_time").as_double(), 7.0);
564643
EXPECT_EQ(node->get_parameter("test.rotate_to_goal_heading").as_bool(), true);
565644
EXPECT_EQ(node->get_parameter("test.rotate_to_heading_once").as_bool(), true);
645+
EXPECT_EQ(node->get_parameter("test.closed_loop").as_bool(), false);
566646
}

0 commit comments

Comments
 (0)