Abstract: Knowing how to write an action server is very useful in robot software development. This guidance will focus on explaining the missing part from the official ROS tutorial -- the relation between an action server, and its generated messages.
There are three ways of passing data in ROS:
- regular topics: the regulars
- service calls: only putting data on the bus when you requested
- action calls: real world long time actions, ROS method for threading
1&2 should always be in computer time and return the data without a human-noticeable delay. I'm putting down here an example to help you to understand a service call scenario better. Therefore, you can easily decide, something should be a service or an action.
Service call example
Getting a robot status data. When you have a case that you want to get the robot status(which may contain you raw sensor data as well as some calculation between the data), now you notice that "I don't need this robot status very often, only give me when a user has asked for it; however, ROS, by default, if you have a node subscribe to a topic, then this topic will keep publishing. Now a service call - method will be very efficient at here. You will get the status data only when you make a service request, and this topic will not take your bandwidth away.
Package File Structure
I expect you have gone through the ROS Actionlib tutorial already and know how to create an action message. So you should have some package containing a similar file structure.
my_package ├── action │ └── Fetch.action ├── launch │ └── automation.launch ├── msg │ └── ActuatorCMD.msg ├── scripts │ ├── action_controller.py │ └── action_fetch_server.py ├── package.xml ├── CMakeLists.txt └── README.md
The Confusion In Generated Messages
Here is my custom action message, same pattern from ROS tutorial:
# Define the goal string cmd_id float32 distance --- # Define the result string cmd_id bool is_reached --- # Define a feedback message string cmd_id float32 error
Messages with "Action" keyword in it
Now let me explain the magic behind it. After you
catkin_make the package, these seven messages will be generated:
$ ls devel/share/my_package/msg/ FetchAction.msg # Feed this name for your callback function's topic type FetchActionGoal.msg # The msgs with a word Action in it FetchActionResult.msg # are all used as the topic callback msg type FetchActionFeedback.msg # that you should use when declaring the function FetchGoal.msg # The msgs without Action in it FetchResult.msg # are the msgs you should use to create an actual FetchFeedback.msg # data structure for the results or feedback
Down below you can find the msgs with an
Action name in it are actually wrapped with a
header and an
actionlib_msgs/GoalID for the actionlib mechnism.
$ cat FetchAction.msg # ====== DO NOT MODIFY! AUTOGENERATED FROM AN ACTION DEFINITION ====== FetchActionGoal action_goal FetchActionResult action_result FetchActionFeedback action_feedback $ cat FetchActionGoal.msg # ====== DO NOT MODIFY! AUTOGENERATED FROM AN ACTION DEFINITION ====== Header header actionlib_msgs/GoalID goal_id FetchGoal goal
Messages without "Action" keyword in it
The msgs without
Action keyword in it are the context you declared between the
--- in the action msg file. This is quite confusing for myself at least the first time, I used all
FetchActionGoal to create the goal msg for my results and it of course not work, furthermore, the error message made me even confused about what was going on.
$ cat FetchGoal.msg # ====== DO NOT MODIFY! AUTOGENERATED FROM AN ACTION DEFINITION ====== # Define the goal string cmd_id float32 distance
Publish Your Action Goal
I believe now you have a clear understanding of when&which message you should use when writing the code. For my habit, I always give out a short example of it: (the examples are usually for a better understanding, they may not actually run if you copy them)
self.server = actionlib.SimpleActionServer('/my_topic/action/fetch_server', FetchAction, self.execute, auto_start=False) result_msg = FetchResult() result_msg.cmd_id = msg.cmd_id result_msg.is_reached = True if self.task_done else False self.server.set_succeeded(result_msg, 'Fetch action succeeded!')
For more about the actionlib workflow, please check out the other post here