Author: methylDragon
Fairly comprehensive ROS crash course!
I'll be adapting code and tutorial content from the ROS Tutorials:http://wiki.ros.org/ROS/Tutorials
and ETHz http://www.rsl.ethz.ch/education-students/lectures/ros.html
- A system with Ubuntu 16.04 installed (no other versions!)
- Linux
- Python 3 and/or C++
- Introduction
- Messages
2.1 Introduction
2.2 Message Files
2.3 rosmsg
2.4 Creating Message Files
2.5 Using Custom Messages (rospy) (roscpp) (Nested Messages) - Services
3.1 Introduction
3.2 Service Files
3.3 rosservice
3.4 Creating Service Files
3.5 Writing Service Nodes (rospy) (roscpp) - Actions
4.1 Introduction
4.2 Action Files
4.3 rostopic for Actions
4.4 Creating Action Files
4.5 Writing Action Nodes (rospy) (roscpp) - Concluding Comparison
So now you know how to write nodes. Ever wondered if you could do more? Of course you can!
You can create custom messages, create nodes just for serving requests, or nodes that carry out actions, and provide feedback.
We're going to talk about messages, services, and actions!
Messages are what are sent to ROS nodes via topics! They are the subject that is published and sent to subscribers!
You can create messages very easily, because ROS has a couple of macros that dynamically generate the language-specific message related code for you! All you need to do, is create the .msg file.
msg: msg files are simple text files that describe the fields of a ROS message. They are used to generate source code for messages in different languages.
Msg files are text files that consist of a single field per line. These are the eligible types:
- int8, int16, int32, int64 (plus uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
- the special Header type
The Header type consists of a timestamp and coordinate frame. A lot of packages use them, and it normally appears as the first line of a .msg file.
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
ROS has a bunch of command line tools to help with messages as well!
# Show displays the contents of the corresponding message file
$ rosmsg show package_name message_name
# If you don't know the package, rosmsg will search for a matching message file!
$ rosmsg show message_name
# You can even use it in combination with rostopic to display type info about the topic!
$ rostopic type /topic_name | rosmsg show
# List displays a list of all messages
$ rosmsg list
# Package displays a list of all messages in a package
$ rosmsg package package_name
# Packages displays a list of all packages with messages
$ rosmsg packages
Let's try making some custom message files!
Reminder: To create a new package,
$ cd <your_workspace_directory>/src $ catkin_create_pkg package_name rospy <any other dependencies, including standard ones!> # Eg. catkin_create_pkg basic_pub_sub rospy std_msgs
1. Go to your package directory
$ roscd msg_example_msgs # Using a message-only package is good practice!
$ mkdir msg # Make a msg folder to keep your message files
$ cd msg
$ touch My_msg.msg # Capitalising the first letter is the convention!
2. Then open that .msg file in your favourite text editor and HAVE AT IT! (Write your data types and names)
Header header
string name
uint8 dragon_rating
3. Then go to package.xml and append these two lines
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
4. Then say hello to our nemesis good old friend CMakeLists.txt
Ensure these lines are present
cmake_minimum_required(VERSION 2.8.3)
project(msg_example_msgs)
# Standard dependencies
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# Your message files!
add_message_files(
FILES
My_msg.msg # <-- Like this one!
)
# You need to include this in as well
# Call it BEFORE catkin_package
generate_messages(
DEPENDENCIES
std_msgs # You might have additional message files from other packages
)
# Additional catkin dependencies
catkin_package(CATKIN_DEPENDS
message_runtime
)
NOTE:
When you are compiling a package that requires message files from a separate message package, be sure to add
add_dependencies(CURRENT_PACKAGE MESSAGE_PACKAGE_generate_messages_cpp)
Otherwise it will fail!
5. Then go back to your workspace root, and rebuild!
$ roscd msg_example_msgs
$ cd ../..
$ catkin_make # or catkin_make install, see what works
In the last tutorial part, we learnt how to use the rospy and roscpp client libraries to write nodes that use in-built messages!
Now I'll show you how to use them to include/import custom messages!
Our message file is structured as such
Header header string name uint8 dragon_rating
Including the message
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ^^^ The encoding line is just because I want to have a cute emoticon ヾ(°∇°*)
import rospy
from msg_example_msgs.msg import My_msg
# Create the message object to publish
msg = My_msg()
Testing it with a publisher
# Then to use it~
def talker():
# My_msg is the topic type (aka the message file the topic takes)
pub = rospy.Publisher("msg_example", My_msg, queue_size = 10)
rospy.init_node("msg_example_node", anonymous = True)
rate = rospy.Rate(10)
while not rospy.is_shutdown():
msg.header.stamp = rospy.Time.now() # Since our message has a header
# The header sequence value will increase once a subscriber is connected
msg.name = "methylDragon"
msg.dragon_rating = 10
rospy.loginfo("FIND MY OUTPUT ON /msg_example ! ヾ(°∇°*)")
pub.publish(msg)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
Including the message
#include <ros/ros.h>
#include <msg_example_msgs/My_msg.h>
msg_example_msgs::My_msg msg;
Testing it with a publisher
int main(int argc, char **argv)
{
ros::init(argc, argv, "msg_example_node");
ros::NodeHandle nh;
ros::Publisher chatter_publisher = nh.advertise<msg_example_msgs::My_msg>("msg_example", 1);
ros::Rate loopRate(10);
unsigned int count = 0;
while (ros::ok())
{
msg_example_msgs::My_msg msg; // Make a new message object
msg.header.stamp = ros::Time::now();
msg.name = "methylDragon";
msg.dragon_rating = 10;
ROS_INFO_STREAM(" . .\n"
" . |\\-^-/| .\n"
"FIND MY OUTPUT ON /msg_example ! /| } O.=.O { |\\ \n");
chatter_publisher.publish(msg);
ros::spinOnce();
loopRate.sleep();
}
return 0;
}
Note: If you're importing messages from other packages, append these lines to package.xml
<build_depend>name_of_package_containing_custom_msg</build_depend> <exec_depend>name_of_package_containing_custom_msg</exec_depend>And this line to CMakeLists.txt
add_dependencies(your_program ${catkin_EXPORTED_TARGETS}) # And then add these lines if you're using roscpp find_package(catkin REQUIRED COMPONENTS std_msgs sensor_msgs) include_directories(include ${catkin_INCLUDE_DIRS}) add_dependencies(your_program ${catkin_EXPORTED_TARGETS}) add_dependencies(your_library ${catkin_EXPORTED_TARGETS})If you're confused, just check the minimal msg_example package in resources and starter code.
Sometimes you might come across nested messages.
For example, the very common geometry_msgs/Twist message (http://docs.ros.org/api/geometry_msgs/html/msg/Twist.html)
The message definition is structured as such
# Twist
Vector3 linear
Vector3 angular
But you notice, that the data type is actually a geometry_msgs/Vector3 message! Which is structured as such
# Vector3
float64 x
float64 y
float64 z
Ok! Cool, we have a nested message. If we want to go ahead and construct a Twist message, we could either:
- Make two Vector3 messages, and pass it in to the original message (the stupid way to do it)
- Or access the class members directly! (THE SMART WAY.)
# Let's say we have a twist message called twist_msg
twist_msg.linear.x = # HERE WE GO!
This should be fairly self explanatory.
A ROS service is a special kind of topic that allows for two-way communication between nodes. (Specifically, request-response communication.)
(Image source: ETHz)
A service is facillitated by two types of nodes:
- The service server advertises the service, and listens for messages sent to the service
- It then computes the response (carrying out any required tasks along the way), and publishes its response to the service!
- The service client publishes to the service, calling it
- Then, it subscribes to the service and waits for the response, stopping you from doing anything else on the terminal the service is called with. (Services are synchronous! As opposed to actions, which are asynchronous and allow you to execute more instructions while the action is running.)
The message types available for use with services are based off of the eligible .msg types, as shown in the previous section of this tutorial.
But the service messages are defined in a special type of file called a .srv file.
Exactly the same as messages!
Service files are just two message files 'smushed' into one!
Eligible message field types
- int8, int16, int32, int64 (plus uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
- the special Header type
Recall the example .msg file:
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
A .srv file is similar (it's just two message files smushed into one, one request, one response.)
The sections are separated by three dashes. It has to be done this way.
request
---
response
Eg:
int64 A
int64 B
---
int64 Sum
NOTE:
rosservice
is different fromrossrv
, which does the same thing thatrosmsg
does for .srv files.
You can use rosservice
in the same way rostopic
is used to publish and subscribe to topics, except now you're calling the service.
# List available services
$ rosservice list
# Show the type of the service (what messages it takes)
$ rosservice type /service_name
# Call a service
$ rosservice call /service_name <argument_1> <argument_2> ...
Okay. Let's make a service file!
Reminder: To create a new package,
$ cd <your_workspace_directory>/src $ catkin_create_pkg package_name rospy <any other dependencies, including standard ones!> # Eg. catkin_create_pkg basic_pub_sub rospy std_msgs
1. Go to your package directory
$ roscd srv_example
$ mkdir srv # Make a srv folder to keep your service files
$ cd srv
$ touch AddTwoInts.srv # Capitalising the first letter is the convention!
2. Then open that .msg file in your favourite text editor and HAVE AT IT! (Write your data types and names)
int64 A
int64 B
---
int64 Sum
3. Then go to package.xml and append these two lines
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
4. Then say hello to our nemesis good old friend CMakeLists.txt
Ensure these lines are present (IN ORDER!)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs # <-- Add these
message_generation # <-- Add these (even though you're generating services)
)
# Uncomment this block
add_service_files(
FILES
AddTwoInts.srv # <-- Add your srv files
)
# Uncomment this block (even though you're generating services)
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(
CATKIN_DEPENDS message_runtime <and your other catkin dependencies> # <-- Add this
)
5. Then go back to your workspace root, and rebuild!
$ roscd srv_example
$ cd ../..
$ catkin_make # or catkin_make install, see what works
Source: http://wiki.ros.org/actionlib_tutorials/Tutorials
This one is a little more involved than just including the messages like we did in the previous part.
Let's use this service
AddTwoInts.srv
int64 A
int64 B
---
int64 Sum
Which should have generated a header file called AddTwoInts.h on build.
Note: You import from your PACKAGE_NAME.srv !
#!/usr/bin/env python
from srv_example.srv import *
import rospy
# Here's your service callback function
def handle_add_two_ints(req):
print("Returning [%s + %s = %s]" % (req.A, req.B, (req.A + req.B)))
return AddTwoIntsResponse(req.A + req.B)
def add_two_ints_server():
rospy.init_node('add_two_ints_server')
# Here's your service hook (AddTwoInts is the service type/file)
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
print("Ready to add two ints.")
rospy.spin()
if __name__ == "__main__":
add_two_ints_server()
#!/usr/bin/env python
import sys
import rospy
from srv_example.srv import *
# Service call handler (arguments are request.A, request.B, as x and y)
def add_two_ints_client(x, y):
# Wait for service to become available
rospy.wait_for_service('add_two_ints')
try:
# The service proxy handles the service call, like a temporary node!
# add_two_ints is the service name
# AddTwoInts is the service type/file
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
resp1 = add_two_ints(x, y)
return resp1.sum
except rospy.ServiceException, e:
print("Service call failed: %s" % e)
# Invalid call reminder
def usage():
return "Usage: %s [x y]" % sys.argv[0]
if __name__ == "__main__":
# If the service call was properly formatted, proceed
if len(sys.argv) == 3:
x = int(sys.argv[1])
y = int(sys.argv[2])
# Else, remind the user of the proper usage
else:
print(usage())
sys.exit(1) # Terminate the script
print("Requesting %s+%s" % (x, y))
print("%s + %s = %s" % (x, y, add_two_ints_client(x, y)))
Remember to
chmod +x scripts/service_node_file.py
!
#include <ros/ros.h>
#include <srv_example/AddTwoInts.h>
// Callback function
bool add(srv_example::AddTwoInts::Request &request,
srv_example::AddTwoInts::Response &response)
{
response.Sum = request.A + request.B;
ROS_INFO("request: x=%ld, y=%ld", (long int)request.A, (long int)request.B);
ROS_INFO(" sending back response: [%ld]", (long int)response.Sum);
return true;
}
int main(int argc, char **argv)
{
// Start the node
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle nh;
// Advertise the service
ros::ServiceServer service = nh.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
// Spin up
ros::spin();
return 0;
}
#include <ros/ros.h>
#include <srv_example/AddTwoInts.h>
#include <cstdlib>
int main(int argc, char **argv) {
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3) {
ROS_INFO("Usage: %s [x y]", argv[0]);
return 1;
}
ros::NodeHandle nh;
ros::ServiceClient client = nh.serviceClient<srv_example::AddTwoInts>("add_two_ints");
srv_example::AddTwoInts service;
service.request.A = atoi(argv[1]);
service.request.B = atoi(argv[2]);
// Service callback function
// client.call(service) calls the client!
if (client.call(service)) {
ROS_INFO("Sum: %ld", (long int)service.response.Sum);
}
else {
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
# Terminal 1
$ roscore
# Terminal 2
$ rosrun srv_example srv_example_server
# Terminal 3
$ rosservice call /add_two_ints 1 3
# Expected outputs
# Terminal 2: Returning [1 + 3 = 4] (Or something along those lines)
# Terminal 3: Sum: 4
# NOTE: Remember to catkin_make and source your devel/setup.bash !
sys.argv
is a list that contains the arguments passed to the script using the command line. It comes from the argc argv (argument count, argument vector) concept in C++.
Basically, the elements of sys.argv are as such.
The first element is the name of the script being called
The elements beyond the first are the arguments passed to the script in order.
SO. This is why you see calls to sys.argv[1], sys.argv[2] for the 2 argument service call in the rospy service example.
A ROS action is just like a ROS service, a special kind of topic that allows for two-way communication between nodes. But with some valuable, and notable differences.
If services have request-response behaviour, actions have goal, result, feedback behaviour. This behaviour is non-blocking, as opposed to services. Which means that the program continues to flow as the action is being executed!
(Image source: ETHz)
The Action topic is special, because it consists of 5 topics under its namespace:
- /goal
- You send the desired goal to trigger the action here
- /cancel
- You send a cancel signal to stop the action execution
- /status
- Shows the status of the action server
- /result
- Shows any end state messages from action execution
- /feedback
- Shows any running messages during action execution
An action is facillitated by two types of nodes:
- The action server advertises the action topic, and listens for messages sent to the /goal or /cancel
- When a valid goal is sent to /goal, it triggers the action server to execute the action it is programmed to carry out, changing its status
- In the midst of executing that action, it constantly publishes to /feedback, giving information of the proceedings
- Upon completion, error, or a stop command (pre-empt) (eg. a command sent to /cancel, which clears the goal on /goal), the action concludes, and publishes to /result the appropriate result message
- The action client publishes to the action topic
- Then, it can poll the /feedback, /status, and /goal topics to get information of the proceedings of the action execution.
- The client is able to continue with its program flow because action calls are not blocking (action calls are asynchronous), as opposed to service calls which are blocking.
Goal To accomplish tasks using actions, we introduce the notion of a goal that can be sent to an ActionServer by an ActionClient. In the case of moving the base, the goal would be a PoseStamped message that contains information about where the robot should move to in the world. For controlling the tilting laser scanner, the goal would contain the scan parameters (min angle, max angle, speed, etc).
Feedback Feedback provides server implementers a way to tell an ActionClient about the incremental progress of a goal. For moving the base, this might be the robot's current pose along the path. For controlling the tilting laser scanner, this might be the time left until the scan completes.
Result A result is sent from the ActionServer to the ActionClient upon completion of the goal. This is different than feedback, since it is sent exactly once. This is extremely useful when the purpose of the action is to provide some sort of information. For move base, the result isn't very important, but it might contain the final pose of the robot. For controlling the tilting laser scanner, the result might contain a point cloud generated from the requested scan.
(Source: http://wiki.ros.org/actionlib)
The message types available for use with actions are based off of the eligible .msg types, just like servces are.
But the action messages are defined in a special type of file called a .action file.
Exactly the same as messages!
Action files are just three message files 'smushed' into one! (As opposed to service files' two.)
Eligible message field types
- int8, int16, int32, int64 (plus uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
- the special Header type
Recall the example .msg file:
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
An .action file is similar (it's just three message files smushed into one, goal, result, feedback)
Again, the sections are separated by three dashes!
goal
---
result
---
feedback
Eg:
#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence
ROS has no special command line interface for interacting with actions. Do it via rostopic
# List available actions
# Look for the /goal, /cancel, /status, /result, /feedback pattern
$ rostopic list
# Show the type of the service (what messages it takes)
$ rostopic type /action_topic
# Call a service
# Just publish the goal!
$ rostopic pub /action_topic/goal goal_msg_type [CONTENT]
There's a handy GUI client if you really want to use it. But using rostopic is probably better...
# Client
$ rosrun actionlib axclient.py
# Server
$ rosrun actionlib axserver.py
Source: http://wiki.ros.org/actionlib_tutorials/Tutorials
Ok, you know the drill. Let's get to writing an action file.
Create a package called simple_action_example
. We'll make more later on, but for now let's just run this through for the simple example! We're going to do a fibonacci number calculator!
1. Go to your package directory
$ roscd simple_action_example
$ mkdir action # Make a srv folder to keep your service files
$ cd action
$ touch Fibonacci.action # Capitalising the first letter is the convention!
2. Then open that .msg file in your favourite text editor and HAVE AT IT! (Write your data types and names)
Make the Fibonacci action!
#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence
3. Then go to package.xml and append these two lines
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
4. Then say hello to our nemesis good old friend CMakeLists.txt
Ensure these lines are present (IN ORDER!)
Note: This is if you want to generate action messages. But if you want to use actionlib, check the appropriate section in 4.5
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
actionlib_msgs # <-- Add this
# Notice there's no message_generation, this is because it's implied by actionlib_msgs
)
# Uncomment this block
add_action_files(
DIRECTORY action
FILES Fibonacci.action
)
# Uncomment this block (even though you're generating services)
generate_messages(
DEPENDENCIES
actionlib_msgs
std_msgs # And any other message packages you're using
)
catkin_package(
CATKIN_DEPENDS roscpp actionlib_msgs <and your other catkin dependencies> # <-- Add this
)
5. Then go back to your workspace root, and rebuild!
$ roscd simple_action_example
$ cd ../..
$ catkin_make # or catkin_make install, see what works
If you built this, you should find that in devel/include/simple_action_example, that there are 10 files that were created.
FibonacciActionFeedback.h FibonacciAction.h FibonacciFeedback.h FibonacciResult.h
FibonacciActionGoal.h FibonacciActionResult.h FibonacciGoal.h
Pretty cool eh!
Source: http://wiki.ros.org/actionlib_tutorials/Tutorials (With some additions from me)
Check the minimal packages for implementation.
#! /usr/bin/env python
import rospy
import actionlib
import simple_action_example.msg
# Create the action server as a class
# The object is just in-case Python 2 is being used to interpret this
# https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python
class FibonacciAction(object):
# Create the Feedback and Result messages
_feedback = simple_action_example.msg.FibonacciFeedback()
_result = simple_action_example.msg.FibonacciResult()
# Constructor
def __init__(self, name):
self._action_name = name
# Hook callbacks
self._as = actionlib.SimpleActionServer(self._action_name,
simple_action_example.msg.FibonacciAction,
execute_cb=self.execute_cb,
auto_start = False)
# Start the action server
self._as.start()
# Goal execution callback
def execute_cb(self, goal):
# helper variables
r = rospy.Rate(1)
success = True
# append the seeds for the fibonacci sequence
self._feedback.sequence = []
self._feedback.sequence.append(0)
self._feedback.sequence.append(1)
# Publish info
rospy.loginfo('%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i' % (self._action_name, goal.order, self._feedback.sequence[0], self._feedback.sequence[1]))
# Execute the action
for i in range(1, goal.order):
# Check that preempt has not been requested by the client
# If yes, kill the action
if self._as.is_preempt_requested():
rospy.loginfo('%s: Preempted' % self._action_name)
self._as.set_preempted()
success = False
break
self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
# publish the feedback
self._as.publish_feedback(self._feedback)
# this step is not necessary, the sequence is computed at 1 Hz for demonstration purposes
r.sleep()
if success:
self._result.sequence = self._feedback.sequence
rospy.loginfo('%s: Succeeded' % self._action_name)
self._as.set_succeeded(self._result)
if __name__ == '__main__':
rospy.init_node('fibonacci')
server = FibonacciAction(rospy.get_name())
rospy.spin()
#! /usr/bin/env python
from __future__ import print_function # Lets you print like Python 3
import rospy
import actionlib
import simple_action_example.msg
def fibonacci_client():
# SimpleActionClient construction, targeting the fibonacci topic of type Fibonacci
client = actionlib.SimpleActionClient('fibonacci',
simple_action_example.msg.FibonacciAction)
# Waits until the action server has started up and started
# listening for goals. (So the goals aren't ignored.)
client.wait_for_server()
# Creates a goal to send to the action server.
goal = simple_action_example.msg.FibonacciGoal(order=20)
# Sends the goal to the action server.
client.send_goal(goal)
# Waits for the server to finish performing the action.
client.wait_for_result()
# Prints out the result of executing the action
return client.get_result() # A FibonacciResult
if __name__ == '__main__':
try:
# Initializes a rospy node so that the SimpleActionClient can
# publish and subscribe over ROS.
rospy.init_node('fibonacci_client_py')
result = fibonacci_client()
print("Result:", ', '.join([str(n) for n in result.sequence]))
except rospy.ROSInterruptException:
print("program interrupted before completion", file=sys.stderr)
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include <simple_action_example/FibonacciAction.h>
class FibonacciAction
{
protected:
ros::NodeHandle nh_;
// NodeHandle instance must be created before this line. Otherwise strange error occurs.
actionlib::SimpleActionServer<simple_action_example::FibonacciAction> as_;
std::string action_name_;
// create messages that are used to published feedback/result
simple_action_example::FibonacciFeedback feedback_;
simple_action_example::FibonacciResult result_;
public:
// Our constructor (with a cool initialisation list!)
// https://stackoverflow.com/questions/2785612/c-what-does-the-colon-after-a-constructor-mean
FibonacciAction(std::string name) :
// Bind the callback to the action server. False is for thread spinning
as_(nh_, name, boost::bind(&FibonacciAction::executeCB, this, _1), false),
action_name_(name)
{
// Start the action server
as_.start();
}
// Destructor
~FibonacciAction(void)
{
}
// Execute action callback (passing the goal via reference)
void executeCB(const simple_action_example::FibonacciGoalConstPtr &goal)
{
// helper variables
ros::Rate r(1);
bool success = true;
// push_back the seeds for the fibonacci sequence
feedback_.sequence.clear();
feedback_.sequence.push_back(0);
feedback_.sequence.push_back(1);
// publish info to the console for the user
ROS_INFO("%s: Executing, creating fibonacci sequence of order %i with seeds %i, %i", action_name_.c_str(), goal->order, feedback_.sequence[0], feedback_.sequence[1]);
// start executing the action (i <= goal->order, as goal is a pointer)
for(int i=1; i<=goal->order; i++)
{
// check that preempt has not been requested by the client
if (as_.isPreemptRequested() || !ros::ok())
{
ROS_INFO("%s: Preempted", action_name_.c_str());
// set the action state to preempted
as_.setPreempted();
success = false;
break;
}
// Add the number to the feedback to be fed back
feedback_.sequence.push_back(feedback_.sequence[i] + feedback_.sequence[i-1]);
// publish the feedback
as_.publishFeedback(feedback_);
// this sleep is not necessary, the sequence is computed at 1 Hz for demonstration purposes
r.sleep();
}
if(success)
{
result_.sequence = feedback_.sequence;
ROS_INFO("%s: Succeeded", action_name_.c_str());
// set the action state to succeeded
as_.setSucceeded(result_);
}
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "fibonacci");
// Create an action server object and spin ROS
FibonacciAction fibonacci("fibonacci");
ros::spin();
return 0;
}
#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h>
#include <actionlib/client/terminal_state.h>
#include <simple_action_example/FibonacciAction.h>
int main (int argc, char **argv)
{
// Init ROS node called test_fibonacci
ros::init(argc, argv, "test_fibonacci");
// create the action client
// true causes the client to spin its own thread
actionlib::SimpleActionClient<simple_action_example::FibonacciAction> ac("fibonacci", true);
ROS_INFO("Waiting for action server to start.");
// wait for the action server to start
ac.waitForServer(); //will wait for infinite time
ROS_INFO("Action server started, sending goal.");
// send a goal to the action
simple_action_example::FibonacciGoal goal;
goal.order = 20;
ac.sendGoal(goal);
//wait for the action to return
bool finished_before_timeout = ac.waitForResult(ros::Duration(30.0));
if (finished_before_timeout)
{
actionlib::SimpleClientGoalState state = ac.getState();
ROS_INFO("Action finished: %s",state.toString().c_str());
}
else
ROS_INFO("Action did not finish before the time out.");
//exit
return 0;
}
Example minimal file from the tutorials:
cmake_minimum_required(VERSION 2.8.3)
project(simple_action_example)
find_package(catkin REQUIRED COMPONENTS roscpp actionlib actionlib_msgs)
# Since we added Boost
find_package(Boost REQUIRED COMPONENTS system)
add_action_files(
DIRECTORY action
FILES Fibonacci.action
)
generate_messages(
DEPENDENCIES actionlib_msgs std_msgs
)
catkin_package(
CATKIN_DEPENDS actionlib_msgs
)
include_directories(include ${catkin_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
# Link the node
add_executable(fibonacci_server src/fibonacci_server.cpp)
target_link_libraries(
fibonacci_server
${catkin_LIBRARIES}
)
add_dependencies(
fibonacci_server
# The one below is the name of your package!!
${simple_action_example_EXPORTED_TARGETS}
)
# Link the node
add_executable(fibonacci_client src/fibonacci_client.cpp)
target_link_libraries(
fibonacci_client
${catkin_LIBRARIES}
)
add_dependencies(
fibonacci_client
# The one below is the name of your package!!
${simple_action_example_EXPORTED_TARGETS}
)
Easy! But remember that you will need to echo the /feedback topic to see anything worthwhile!
If you want to self-publish a goal, remember to send it with a header and goal ID!
# Terminal 1
$ roscore
# Terminal 2
$ rosrun simple_action_example fibonacci_server
# Terminal 3
$ rosrun simple_action_example fibonacci_client
# Then just echo the topics using rostopic echo /fibonacci/???
# And view the graph (using rqt_graph) if you want to!
Source: http://wiki.ros.org/actionlib_tutorials/Tutorials (With some additions from me)
Source: http://wiki.ros.org/actionlib_tutorials/Tutorials (With some additions from me)
(Image source: ETHz)
. .
. |\-^-/| .
/| } O.=.O { |\