Skip to content

Commit 28448f0

Browse files
jacobperrondirk-thomas
authored andcommitted
Add RotateAbsolute action to turtlesim (ros#62)
* [turtlesim] Add action interface for rotating to absolute angle Signed-off-by: Jacob Perron <[email protected]> * Implement RotateAbsolute action Only one action is executed at a time. Any new actions override existing ones. If a velocity command is received while executing the action, then the goal is aborted. Signed-off-by: Jacob Perron <[email protected]> * Support triggering rotation action with keyboard teleop Use the keys g/b/v/c/d/e/r/t to rotate the turtle to absolute orientations and 'f' to cancel a rotation. Signed-off-by: Jacob Perron <[email protected]> * Catch exceptions when attempting to cancel goal Signed-off-by: Jacob Perron <[email protected]> * Add back input.shutdown() Signed-off-by: Jacob Perron <[email protected]> * Add back rclcpp::shutdown Signed-off-by: Jacob Perron <[email protected]> * Define TWO_PI from PI in header Signed-off-by: Jacob Perron <[email protected]> * match style
1 parent a441ae8 commit 28448f0

File tree

6 files changed

+267
-16
lines changed

6 files changed

+267
-16
lines changed

turtlesim/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ find_package(ament_index_cpp REQUIRED)
1414
find_package(geometry_msgs REQUIRED)
1515
find_package(Qt5 REQUIRED COMPONENTS Widgets)
1616
find_package(rclcpp REQUIRED)
17+
find_package(rclcpp_action REQUIRED)
1718
find_package(rosidl_default_generators REQUIRED)
1819
find_package(std_msgs REQUIRED)
1920
find_package(std_srvs REQUIRED)
2021

2122
include_directories(include ${Qt5Widgets_INCLUDE_DIRS})
2223

2324
rosidl_generate_interfaces(${PROJECT_NAME}
25+
"action/RotateAbsolute.action"
2426
"msg/Color.msg"
2527
"msg/Pose.msg"
2628
"srv/Kill.srv"
@@ -29,7 +31,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
2931
"srv/TeleportAbsolute.srv"
3032
"srv/TeleportRelative.srv")
3133

32-
set(dependencies "rclcpp" "ament_index_cpp" "geometry_msgs" "std_msgs" "std_srvs")
34+
set(dependencies "ament_index_cpp" "geometry_msgs" "rclcpp" "rclcpp_action" "std_msgs" "std_srvs")
3335

3436
set(turtlesim_node_SRCS
3537
src/turtlesim.cpp
@@ -50,6 +52,7 @@ rosidl_target_interfaces(turtlesim_node ${PROJECT_NAME} "rosidl_typesupport_cpp"
5052

5153
add_executable(turtle_teleop_key tutorials/teleop_turtle_key.cpp)
5254
ament_target_dependencies(turtle_teleop_key ${dependencies})
55+
rosidl_target_interfaces(turtle_teleop_key ${PROJECT_NAME} "rosidl_typesupport_cpp")
5356
add_executable(draw_square tutorials/draw_square.cpp)
5457
ament_target_dependencies(draw_square ${dependencies})
5558
rosidl_target_interfaces(draw_square ${PROJECT_NAME} "rosidl_typesupport_cpp")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# The desired heading in radians
2+
float32 theta
3+
---
4+
# The angular displacement in radians to the starting position
5+
float32 delta
6+
---
7+
# The remaining rotation in radians
8+
float32 remaining

turtlesim/include/turtlesim/turtle.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@
3333
// This prevents a MOC error with versions of boost >= 1.48
3434
#ifndef Q_MOC_RUN // See: https://bugreports.qt-project.org/browse/QTBUG-22829
3535
# include <rclcpp/rclcpp.hpp>
36+
# include <rclcpp_action/rclcpp_action.hpp>
3637

3738
# include <geometry_msgs/msg/twist.hpp>
39+
# include <turtlesim/action/rotate_absolute.hpp>
3840
# include <turtlesim/msg/color.hpp>
3941
# include <turtlesim/msg/pose.hpp>
4042
# include <turtlesim/srv/set_pen.hpp>
@@ -48,13 +50,16 @@
4850
#include <QPointF>
4951

5052
#define PI 3.14159265
53+
#define TWO_PI 2.0 * PI
5154

5255
namespace turtlesim
5356
{
5457

5558
class Turtle
5659
{
5760
public:
61+
using RotateAbsoluteGoalHandle = rclcpp_action::ServerGoalHandle<turtlesim::action::RotateAbsolute>;
62+
5863
Turtle(rclcpp::Node::SharedPtr& nh, const std::string& real_name, const QImage& turtle_image, const QPointF& pos, float orient);
5964

6065
bool update(double dt, QPainter& path_painter, const QImage& path_image, qreal canvas_width, qreal canvas_height);
@@ -64,6 +69,7 @@ class Turtle
6469
bool setPenCallback(const turtlesim::srv::SetPen::Request::SharedPtr, turtlesim::srv::SetPen::Response::SharedPtr);
6570
bool teleportRelativeCallback(const turtlesim::srv::TeleportRelative::Request::SharedPtr, turtlesim::srv::TeleportRelative::Response::SharedPtr);
6671
bool teleportAbsoluteCallback(const turtlesim::srv::TeleportAbsolute::Request::SharedPtr, turtlesim::srv::TeleportAbsolute::Response::SharedPtr);
72+
void rotateAbsoluteAcceptCallback(const std::shared_ptr<RotateAbsoluteGoalHandle>);
6773

6874
void rotateImage();
6975

@@ -86,6 +92,12 @@ class Turtle
8692
rclcpp::Service<turtlesim::srv::SetPen>::SharedPtr set_pen_srv_;
8793
rclcpp::Service<turtlesim::srv::TeleportRelative>::SharedPtr teleport_relative_srv_;
8894
rclcpp::Service<turtlesim::srv::TeleportAbsolute>::SharedPtr teleport_absolute_srv_;
95+
rclcpp_action::Server<turtlesim::action::RotateAbsolute>::SharedPtr rotate_absolute_action_server_;
96+
97+
std::shared_ptr<RotateAbsoluteGoalHandle> rotate_absolute_goal_handle_;
98+
std::shared_ptr<turtlesim::action::RotateAbsolute::Feedback> rotate_absolute_feedback_;
99+
std::shared_ptr<turtlesim::action::RotateAbsolute::Result> rotate_absolute_result_;
100+
qreal rotate_absolute_start_orient_;
89101

90102
rclcpp::Time last_command_time_;
91103

turtlesim/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<depend>ament_index_cpp</depend>
2828
<depend>geometry_msgs</depend>
2929
<depend>rclcpp</depend>
30+
<depend>rclcpp_action</depend>
3031
<depend>std_msgs</depend>
3132
<depend>std_srvs</depend>
3233

turtlesim/src/turtle.cpp

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
namespace turtlesim
4242
{
4343

44+
static float normalizeAngle(float angle)
45+
{
46+
return angle - (TWO_PI * std::floor((angle + PI) / (TWO_PI)));
47+
}
48+
4449
Turtle::Turtle(rclcpp::Node::SharedPtr& nh, const std::string& real_name, const QImage& turtle_image, const QPointF& pos, float orient)
4550
: nh_(nh)
4651
, turtle_image_(turtle_image)
@@ -60,6 +65,20 @@ Turtle::Turtle(rclcpp::Node::SharedPtr& nh, const std::string& real_name, const
6065
set_pen_srv_ = nh_->create_service<turtlesim::srv::SetPen>(real_name + "/set_pen", std::bind(&Turtle::setPenCallback, this, std::placeholders::_1, std::placeholders::_2));
6166
teleport_relative_srv_ = nh_->create_service<turtlesim::srv::TeleportRelative>(real_name + "/teleport_relative", std::bind(&Turtle::teleportRelativeCallback, this, std::placeholders::_1, std::placeholders::_2));
6267
teleport_absolute_srv_ = nh_->create_service<turtlesim::srv::TeleportAbsolute>(real_name + "/teleport_absolute", std::bind(&Turtle::teleportAbsoluteCallback, this, std::placeholders::_1, std::placeholders::_2));
68+
rotate_absolute_action_server_ = rclcpp_action::create_server<turtlesim::action::RotateAbsolute>(
69+
nh,
70+
real_name + "/rotate_absolute",
71+
[](const rclcpp_action::GoalUUID &, std::shared_ptr<const turtlesim::action::RotateAbsolute::Goal>)
72+
{
73+
// Accept all goals
74+
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
75+
},
76+
[](const std::shared_ptr<RotateAbsoluteGoalHandle>)
77+
{
78+
// Accept all cancel requests
79+
return rclcpp_action::CancelResponse::ACCEPT;
80+
},
81+
std::bind(&Turtle::rotateAbsoluteAcceptCallback, this, std::placeholders::_1));
6382

6483
last_command_time_ = nh_->now();
6584

@@ -73,6 +92,14 @@ void Turtle::velocityCallback(const geometry_msgs::msg::Twist::SharedPtr vel)
7392
last_command_time_ = nh_->now();
7493
lin_vel_ = vel->linear.x;
7594
ang_vel_ = vel->angular.z;
95+
96+
// Abort any active action
97+
if (rotate_absolute_goal_handle_)
98+
{
99+
RCLCPP_WARN(nh_->get_logger(), "Velocity command received during rotation action. Aborting action");
100+
rotate_absolute_goal_handle_->abort(rotate_absolute_result_);
101+
rotate_absolute_goal_handle_ = nullptr;
102+
}
76103
}
77104

78105
bool Turtle::setPenCallback(const turtlesim::srv::SetPen::Request::SharedPtr req, turtlesim::srv::SetPen::Response::SharedPtr)
@@ -105,6 +132,20 @@ bool Turtle::teleportAbsoluteCallback(const turtlesim::srv::TeleportAbsolute::Re
105132
return true;
106133
}
107134

135+
void Turtle::rotateAbsoluteAcceptCallback(const std::shared_ptr<RotateAbsoluteGoalHandle> goal_handle)
136+
{
137+
// Abort any existing goal
138+
if (rotate_absolute_goal_handle_)
139+
{
140+
RCLCPP_WARN(nh_->get_logger(), "Rotation action received before a previous action finished. Aborting previous action");
141+
rotate_absolute_goal_handle_->abort(rotate_absolute_result_);
142+
}
143+
rotate_absolute_goal_handle_ = goal_handle;
144+
rotate_absolute_feedback_.reset(new turtlesim::action::RotateAbsolute::Feedback);
145+
rotate_absolute_result_.reset(new turtlesim::action::RotateAbsolute::Result);
146+
rotate_absolute_start_orient_ = orient_;
147+
}
148+
108149
void Turtle::rotateImage()
109150
{
110151
QTransform transform;
@@ -148,6 +189,47 @@ bool Turtle::update(double dt, QPainter& path_painter, const QImage& path_image,
148189

149190
teleport_requests_.clear();
150191

192+
// Process any action requests
193+
if (rotate_absolute_goal_handle_)
194+
{
195+
// Check if there was a cancel request
196+
if (rotate_absolute_goal_handle_->is_canceling())
197+
{
198+
RCLCPP_INFO(nh_->get_logger(), "Rotation action canceled");
199+
rotate_absolute_goal_handle_->canceled(rotate_absolute_result_);
200+
rotate_absolute_goal_handle_ = nullptr;
201+
lin_vel_ = 0.0;
202+
ang_vel_ = 0.0;
203+
}
204+
else
205+
{
206+
float theta = normalizeAngle(rotate_absolute_goal_handle_->get_goal()->theta);
207+
float remaining = normalizeAngle(theta - static_cast<float>(orient_));
208+
209+
// Update result
210+
rotate_absolute_result_->delta = normalizeAngle(static_cast<float>(rotate_absolute_start_orient_ - orient_));
211+
212+
// Update feedback
213+
rotate_absolute_feedback_->remaining = remaining;
214+
rotate_absolute_goal_handle_->publish_feedback(rotate_absolute_feedback_);
215+
216+
// Check stopping condition
217+
if (fabs(normalizeAngle(static_cast<float>(orient_) - theta)) < 0.02)
218+
{
219+
rotate_absolute_goal_handle_->succeed(rotate_absolute_result_);
220+
rotate_absolute_goal_handle_ = nullptr;
221+
lin_vel_ = 0.0;
222+
ang_vel_ = 0.0;
223+
}
224+
else
225+
{
226+
lin_vel_ = 0.0;
227+
ang_vel_ = remaining < 0.0 ? -1.0 : 1.0;
228+
last_command_time_ = nh_->now();
229+
}
230+
}
231+
}
232+
151233
if (nh_->now() - last_command_time_ > rclcpp::Duration(1.0, 0))
152234
{
153235
lin_vel_ = 0.0;
@@ -158,7 +240,7 @@ bool Turtle::update(double dt, QPainter& path_painter, const QImage& path_image,
158240

159241
orient_ = orient_ + ang_vel_ * dt;
160242
// Keep orient_ between -pi and +pi
161-
orient_ -= 2*PI * std::floor((orient_ + PI)/(2*PI));
243+
orient_ = normalizeAngle(orient_);
162244
pos_.rx() += std::cos(orient_) * lin_vel_ * dt;
163245
pos_.ry() += - std::sin(orient_) * lin_vel_ * dt;
164246

0 commit comments

Comments
 (0)