Hello ROS Developers!
In today’s video we are going learn how to create a ROS Action Server.
The commands used in this video can be typed on your own computer if you already has ROS installed, but to make things easier, I’ll use Robot Ignite Academy, which is the best tool if you want to Learn ROS Really Fast.
Before we start, if you are new to ROS, I highly recommend you taking any of the following courses on Robot Ignite Academy:
Ok, let’s get started.
Create your own ROS Action Messages
When creating an Action Server you can use existing Action Messages, but since we are here to learn, we are going to create and use our own ROS Messages. We already have a post (https://www.theconstruct.ai/ros-5-mins-032-compile-ros-action-messages/) showing how to create your own ROS Messages and compile them. Please refer to it before proceeding in this post, since we will use the package and messages created there.
Creating the ROS Action Server
If you followed the post aforementioned on how to create your own ROS Messages, with the tree .
command on the ~/catkin_ws/src
folder you should have a package called actions_tutorial
with the WashTheDishes.action
action message, something like the output below:
user:~/catkin_ws/src$ tree . . ├── actions_tutorial │ ├── action │ │ └── WashTheDishes.action │ ├── CMakeLists.txt │ ├── package.xml │ └── src └── CMakeLists.txt -> /opt/ros/kinetic/share/catkin/cmake/toplevel.cmake 3 directories, 4 files
In order to create the action server, let’s create a folder called scripts
on our actions_tutorial
package. On the scripts
folder let’s create a file called action_server.py
and make it executable. Let’s use the commands below to accomplish that:
cd ~/catkin_ws/src/actions_tutorial mkdir scripts cd scripts/ touch action_server.py chmod +x action_server.py
Let’s insert the code below on our action_server.py
and see what it really does:
#! /usr/bin/env python import rospy import actionlib from actions_tutorial.msg import WashTheDishesAction, WashTheDishesFeedback, WashTheDishesResult class ActionServer(): def __init__(self): self.a_server = actionlib.SimpleActionServer( "wash_dishes_as", WashTheDishesAction, execute_cb=self.execute_cb, auto_start=False) self.a_server.start() def execute_cb(self, goal): success = True last_dish_washed = '' feedback = WashTheDishesFeedback() result = WashTheDishesResult() rate = rospy.Rate(1) for i in range(0, goal.number_of_minutes): if self.a_server.is_preempt_requested(): self.a_server.set_preempted() success = False break last_dish_washed = 'bowl-' + str(i) feedback.last_dish_washed = last_dish_washed result.dishes_washed.append(last_dish_washed) self.a_server.publish_feedback(feedback) rate.sleep() if success: self.a_server.set_succeeded(result) if __name__ == "__main__": rospy.init_node("action_server") s = ActionServer() rospy.spin()
Let’s check what the first lines do in our code:
#! /usr/bin/env python import rospy import actionlib from actions_tutorial.msg import WashTheDishesAction, WashTheDishesFeedback, WashTheDishesResult
In the first line, we tell Linux that our code is executed with Python. We then import rospy
because we are creating our ROS program using python. Additionally, we import actionlib
because it is necessary to create our ROS Action Server. Lastly, we import our own Action Messages that will allow our robot to “wash the dishes”.
Let’s now analyze the other lines of code:
class ActionServer(): def __init__(self): self.a_server = actionlib.SimpleActionServer( "wash_dishes_as", WashTheDishesAction, execute_cb=self.execute_cb, auto_start=False) self.a_server.start()
Here we create our action server class, which we called ActionServer
. We then have our default constructor where we define our a_server
, which is an instance of actionlib.SimpleActionServer
. The first parameter is the name of our server once it is running (we called wash_dishes_as
), the second parameter is the specification of our action, in this case, WashTheDishesAction
. The execute_cb
parameter defines the function that will be executed once a client calls our server.
Now let’s dive into the execute_cb
method:
def execute_cb(self, goal): success = True last_dish_washed = '' feedback = WashTheDishesFeedback() result = WashTheDishesResult() rate = rospy.Rate(1) for i in range(0, goal.number_of_minutes): if self.a_server.is_preempt_requested(): self.a_server.set_preempted() success = False break last_dish_washed = 'bowl-' + str(i) feedback.last_dish_washed = last_dish_washed result.dishes_washed.append(last_dish_washed) self.a_server.publish_feedback(feedback) rate.sleep() if success: self.a_server.set_succeeded(result)
Once the action server is called, our execute_cb
method is executed with the goal
provided by the user. In the first lines, we basically define the variables we are going to use.
Our action is supposed to wash dishes for the number of minutes provided by the user on the goal.number_of_minutes
parameter, but since we are not really washing dishes, we are basically iterating on that number and sending a feedback message to the user.
A ROS Action normally takes a long time to finish, so the user can change his mind and cancel it at any time. We check if this is the case with if self.a_server.is_preempt_requested():
. If so, we break the loop and our server finishes its work.
If the user didn’t cancel, we keep doing our job, which will be washing dishes. We fill the feedback
and result
variables with the last dish washed. If you remember the post mentioned at the beginning, we see that our action has int32 number_of_minutes
as the goal, string last_dish_washed
as the feedback and string[] dishes_washed
as a result.
Once the robot worked the for the time asked on the goal.number_of_minutes
parameter, we just send the result to the user with self.a_server.set_succeeded(result)
.
The last part of the code (shown below) we basically instantiate our ROS Node
as well as the Action Server
. We call ros.spin()
to keep our program running forever, until someone kills it with ctrl+c
.
if __name__ == "__main__": rospy.init_node("action_server") s = ActionServer() rospy.spin()
Awesome, we now have everything in place. Let’s now run our server and send it a goal.
Running the ROS Action Server
To launch our server, the messages need to be compiled. If you missed this step when creating your message, you can just do it with the commands below:
cd ~/catkin_ws/ catkin_make
If everything went ok, you should have a message at the end saying [100%] Built target actions_tutorial_generate_messages
.
With the messages compiled, we need to source
our catkin_ws
just to make sure ROS can find our compiled messages. We will do that on the same shell (terminal) used to launch our server.
Ok, let’s launch our server:
source ~/catkin_ws/devel/setup.bash rosrun actions_tutorial action_server.py
If no error message is shown, our service is running. We can confirm that by running the command rosnode list | grep server
which should output /action_server
, since is the name we defined on our default constructor.
We can confirm that our node is providing the wash the dishes
service with rosnode info /action_server
, which should output something like:
user:~$ rosnode info /action_server -------------------------------------------------------------------------------- Node [/action_server] Publications: * /rosout [rosgraph_msgs/Log] * /wash_dishes_as/feedback [actions_tutorial/WashTheDishesActionFeedback] * /wash_dishes_as/result [actions_tutorial/WashTheDishesActionResult] * /wash_dishes_as/status [actionlib_msgs/GoalStatusArray] Subscriptions: * /clock [rosgraph_msgs/Clock] * /wash_dishes_as/cancel [unknown type] * /wash_dishes_as/goal [unknown type]
We see that our node publishes feedback
, status
and result
. If we subscribe to the feedback
topic we will see the feedback messages published when the user sends a goal. Let’s subscribe with the following command in a different shell: rostopic echo /wash_dishes_as/feedback
We can also see the result once the action is finished by subscribing to the result
topic in a different shell. Let’s do it with the next command: rostopic echo /wash_dishes_as/result
With everything in place, we can now send our goal. With the rostopic info /action_server
aforementioned, we see that our node is subscribed to the /wash_dishes_as/goal
topic. This is exactly the topic we use to send goals.
In order to send a goal we can publish on that topic with rostopic pub /wash_dishes_as/goal TAB+TAB+TAB
By pressing TAB+TAB
ROS automatically completes the type of the message as well as its fields. If everything goes ok you should have something like the output below:
rostopic pub /wash_dishes_as/goal actions_tutorial/WashTheDishesActionGoal "header: seq: 0 stamp: secs: 0 nsecs: 0 frame_id: '' goal_id: stamp: secs: 0 nsecs: 0 id: '' goal: number_of_minutes: 0"
You can see that in the last line we have number_of_minutes: 0
. With the left arrow key you can reach that field and change its value. Let’s change it to 5 and after the "
we write --once
to publish the message only once. After that we press enter. To make sure we are doing right, before pressing enter we should have something like:
rostopic pub /wash_dishes_as/goal actions_tutorial/WashTheDishesActionGoal "header: seq: 0 stamp: secs: 0 nsecs: 0 frame_id: '' goal_id: stamp: secs: 0 nsecs: 0 id: '' goal: number_of_minutes: 5" --once
You will see nothing on the shell you execute this command, but if you go to the shell were you executedrostopic echo /wash_dishes_as/feedback
, you should see 5 feedback messages saying that the dishes are being washed.
Also, if you go to the shell where you typed rostopic echo /wash_dishes_as/result
, you should see the result message, like the one below:
user:~$ rostopic echo /wash_dishes_as/result WARNING: no messages received and simulated time is active. Is /clock being published? header: seq: 2 stamp: secs: 4155 nsecs: 895000000 frame_id: '' status: goal_id: stamp: secs: 4150 nsecs: 891000000 id: "/action_server-2-4150.891000000" status: 3 text: '' result: dishes_washed: [bowl-0, bowl-1, bowl-2, bowl-3, bowl-4] ---
Note that on the last line we have the name of the dishes washed: dishes_washed: [bowl-0, bowl-1, bowl-2, bowl-3, bowl-4]
A video to make your life easier
Congratulations. You now have an action server fully operational. If you liked the post, great, but if you want also to watch a video showing all the process described in this post, we have it here:
Now that we know how to create an Action Server, let’s learn how to create an Action Client? If you are interested, please have a look at the next post: https://www.theconstruct.ai/ros-5-mins-035-create-ros-action-client/
To conclude, if you liked the video, please give us a thumbs up on YouTube. Additionally, should you have any feedback messages, please leave them on the comments section of the video. You can also subscribe to our youtube channel and press the bell to be notified about the videos we publish every day.
Keep pushing our ROS Learning.
0 Comments