Edit on Github

Writing an Action Server (Python)

In this tutorial, we look at implementing an action server in Python.

Make sure you have satisfied all prerequisites.

Executing Goals

Let’s focus on writing an action server that computes the Fibonacci sequence using the action we created in the Creating an Action tutorial.

To keep things simple, we’ll scope this tutorial to a single file. Open a new file, let’s call it fibonacci_action_server.py, and add the following boilerplate code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import rclpy
from rclpy.node import Node


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')


def main(args=None):
    rclpy.init(args=args)

    fibonacci_action_server = FibonacciActionServer()

    rclpy.spin(fibonacci_action_server)


if __name__ == '__main__':
    main()

We’ve defined a class FibonacciActionServer that is a subclass of Node. The class is initialized by calling the Node constructor, naming our node “fibonacci_action_server”:

        super().__init__('fibonacci_action_server')

After the class defintion, we define a function main() that initializes ROS, creates an instance of our FibonacciActionServer node, and calls rclpy.spin() on our node. The spin will keep our action server alive and responsive to incoming goals. Finally, we call main() in the entry point of our Python program.

Next, we’ll import our Fibonacci action definition and create an action server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from action_tutorials.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        return Fibonacci.Result()

The action server requires four arguments:

  1. a ROS node to add the action client to: self.

  2. the type of the action: Fibonacci.

  3. the action name: 'fibonacci'.

  4. a callback function for executing accepted goals: self.execute_callback. This callback must return a result message for the action type.

Note, all goals are accepted by default.

Let’s try running our action server:

# Linux/OSX
python3 fibonacci_action_server.py
# Windows
python fibonacci_action_server.py

In another terminal, we can use the command line interface to send a goal:

ros2 action send_goal fibonacci action_tutorials/action/Fibonacci "{order: 5}"

You should see our logged message “Executing goal…” followed by a warning that the goal state was not set. By default, if the goal handle state is not set in the execute callback it assumes the aborted state.

We can use the method succeed() on the goal handle to indicate that the goal was successful:

1
2
3
4
    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        goal_handle.succeed()
        return Fibonacci.Result()

Now if you restart the action server and send another goal, you should see the goal finished with the status SUCCEEDED.

Alright, let’s make our goal execution actually compute and return the requested Fibonacci sequence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        sequence = [0, 1]

        for i in range(1, goal_handle.request.order):
            sequence.append(sequence[i] + sequence[i-1])

        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = sequence
        return result

After computing the sequence, we assign it to the result message field before returning.

Again, restart the action server and send another goal. You should see the goal finish with the proper result sequence.

Publishing Feedback

One of the nice things about actions is the ability to provide feedback to an action client during goal execution. We can make our action server publish feedback for action clients by calling the goal handle’s publish_feedback() method.

We’ll replace the sequence variable, and use a feedback message to store the sequence instead. After every update of the feedback message in the for-loop, we publish the feedback message and sleep for dramatic effect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import time

import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node

from action_tutorials.action import Fibonacci


class FibonacciActionServer(Node):

    def __init__(self):
        super().__init__('fibonacci_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')

        feedback_msg = Fibonacci.Feedback()
        feedback_msg.partial_sequence = [0, 1]

        for i in range(1, goal_handle.request.order):
            feedback_msg.partial_sequence.append(
                feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])
            self.get_logger().info('Feedback: {0}'.format(feedback_msg.partial_sequence))
            goal_handle.publish_feedback(feedback_msg)
            time.sleep(1)

        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = feedback_msg.partial_sequence
        return result

After restarting the action server, we can confirm that feedback is now published by using the command line tool with the --feedback option:

ros2 action send_goal --feedback fibonacci action_tutorials/action/Fibonacci "{order: 5}"