Writing a Simple Action Server using the Execute Callback (Python)

Description: This tutorial covers using the simple_action_server library to create a Fibonacci action server in Python. This example action server generates a Fibonacci sequence, the goal is the order of the sequence, the feedback is the sequence as it is computed, and the result is the final sequence.

Tutorial Level: BEGINNER

Creating the Action Messages

Before writing an action it is important to define the goal, result, and feedback messages. The action messages are generated automatically from the .action file, for more information on action files see the actionlib documentation. This file defines the type and format of the goal, result, and feedback topics for the action. Create learning_actionlib/action/Fibonacci.action in your favorite editor, and place the following inside it:

#goal definition
int32 order
---
#result definition
int32[] sequence
---
#feedback
int32[] sequence

To manually generate the message files from this file:

$ roscd learning_actionlib
$ rosrun actionlib genaction.py . Fibonacci.action

You will see:

  • Generating for action Fibonacci

To automatically generate the message files during the make process, add the following to CMakeLists.txt (before the rosbuild_init call)

rosbuild_find_ros_package(actionlib)
include(${actionlib_PACKAGE_PATH}/cmake/actionbuild.cmake)
genaction()

Writing a Simple Server

The code and examples used in this tutorial can be found in the actionlib_tutorials package. You may want to read about the actionlib package before starting this tutorial.

The Code

The following code can be found in actionlib_tutorials/simple_action_servers/fibonacci_server.py, and implements a python action server for the fibonacci action.

   1 #! /usr/bin/env python
   2 
   3 import roslib; roslib.load_manifest('actionlib_tutorials')
   4 import rospy
   5 
   6 import actionlib
   7 
   8 import actionlib_tutorials.msg
   9 
  10 class FibonacciAction(object):
  11   # create messages that are used to publish feedback/result
  12   _feedback = actionlib_tutorials.msg.FibonacciFeedback()
  13   _result   = actionlib_tutorials.msg.FibonacciResult()
  14 
  15   def __init__(self, name):
  16     self._action_name = name
  17     self._as = actionlib.SimpleActionServer(self._action_name, actionlib_tutorials.msg.FibonacciAction, execute_cb=self.execute_cb)
  18 
  19   def execute_cb(self, goal):
  20     # helper variables
  21     r = rospy.Rate(1)
  22     success = True
  23 
  24     # append the seeds for the fibonacci sequence
  25     self._feedback.sequence = []
  26     self._feedback.sequence.append(0)
  27     self._feedback.sequence.append(1)
  28 
  29     # publish info to the console for the user
  30     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]))
  31 
  32     # start executing the action
  33     for i in xrange(1, goal.order):
  34       # check that preempt has not been requested by the client
  35       if self._as.is_preempt_requested():
  36         rospy.loginfo('%s: Preempted' % self._action_name)
  37         self._as.set_preempted()
  38         success = False
  39         break
  40       self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
  41       # publish the feedback
  42       self._as.publish_feedback(self._feedback)
  43       # this step is not necessary, the sequence is computed at 1 Hz for demonstration purposes
  44       r.sleep()
  45 
  46     if success:
  47       self._result.sequence = self._feedback.sequence
  48       rospy.loginfo('%s: Succeeded' % self._action_name)
  49       self._as.set_succeeded(self._result)
  50 
  51 if __name__ == '__main__':
  52   rospy.init_node('fibonacci')
  53   FibonacciAction(rospy.get_name())
  54   rospy.spin()

The Code, explained

   6 import actionlib

This line imports the action library used for implementing simple actions.

   8 import actionlib_tutorials.msg

The action specification generates several messages for sending goals, receiving feedback, etc... This line imports the generated messages.

  17     self._as = actionlib.SimpleActionServer(self._action_name, actionlib_tutorials.msg.FibonacciAction, execute_cb=self.execute_cb)

Here, the SimpleActionServer is created, we pass it a name, an action type, and optionally an execute callback. Since we've specified an execute callback in this example, a thread will be spun for us which allows us to take long running actions in a callback received when a new goal comes in.

  19   def execute_cb(self, goal):

This is the execute callback function that we'll run everytime a new goal is received.

  21     r = rospy.Rate(1)
  22     success = True
  23 
  24     # append the seeds for the fibonacci sequence
  25     self._feedback.sequence = []
  26     self._feedback.sequence.append(0)
  27     self._feedback.sequence.append(1)
  28 
  29     # publish info to the console for the user
  30     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]))

Here, the internals of the action are created. In this example rospy.loginfo is published to let the user know that the action is executing.

  32     # start executing the action
  33     for i in xrange(1, goal.order):
  34       # check that preempt has not been requested by the client
  35       if self._as.is_preempt_requested():
  36         rospy.loginfo('%s: Preempted' % self._action_name)
  37         self._as.set_preempted()
  38         success = False

An important component of an action server is the ability to allow an action client to request that the goal under execution be canceled. When a client requests that the current goal be preempted, the action server should cancel the goal, perform any necessary cleanup, and call the set_preempted function, which signals that the action has been preempted by user request. Here, we'll check if we've been preempted every second. We could, alternatively, receive a callback when a preempt request is received.

  40       self._feedback.sequence.append(self._feedback.sequence[i] + self._feedback.sequence[i-1])
  41       # publish the feedback
  42       self._as.publish_feedback(self._feedback)

Here, the Fibonacci sequence is put into the feedback variable and then published on the feedback channel provided by the action server. Then, the action continues looping and publishing feedback.

  46     if success:
  47       self._result.sequence = self._feedback.sequence
  48       rospy.loginfo('%s: Succeeded' % self._action_name)
  49       self._as.set_succeeded(self._result)

Once the action has finished computing the Fibonacci sequence, the action server notifies the action client that the goal is complete by calling set_succeeded.

  51 if __name__ == '__main__':
  52   rospy.init_node('fibonacci')
  53   FibonacciAction(rospy.get_name())
  54   rospy.spin()

Finally, the main function, creates the action server and spins the node.

Running the Action Server

The following command will run the action server.

python fibonacci_server.py

Wiki: actionlib_tutorials/Tutorials/Writing a Simple Action Server using the Execute Callback (Python) (last edited 2010-02-17 21:09:21 by DanLazewatsky)