Creating ROS2 Action in Python
Below are the steps to create a ROS2 action using Python. Notice that we are going to have two packages involved: 1) a package called “my_package” which contains the action file, and 2) a package called “action_nodes_python” which contains the action server and client nodes. Separating the package which contains the action file(s) like this, in general, is a good practice.
In the official example, the action server is wrapped in a package and the action client is wrapped in another package. This is also possible. We will do similarly in this tutorial, i.e., the action server and the action client are wrapped in separate packages, namely “action_server_python” and “action_client_python”.
Step 1: Creating an action file. For ROS2 Foxy, the official documentation only provides the creation of an action file in C++. No Python way is provided. Therefore, here we also use C++ to create an action file.
1.1. Create a folder called “action” (if you still don’t have it) inside a package folder called “my_package”. And then make the action file (.action) called “ActionFile.action”. For example, if the action is to compute Fibonacci numbers, you may call it “Fibonacci.action” which looks like below:
1 2 3 4 5 6 7 8 |
#goal definition int32 order --- #result definition int32[] sequence --- #feedback int32[] partial_sequence |
The lines of three dashes above separate between the request variable(s), the result variable(s), and the feedback variable(s).
1.2. Add the following in package.xml of “my_package” package.
1 2 3 |
<buildtool_depend>rosidl_default_generators</buildtool_depend> <depend>action_msgs</depend> <member_of_group>rosidl_interface_packages</member_of_group> |
1.3. Add the following in CMakeLists.txt of “my_package” package, before ament_package().
1 2 3 |
find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action") |
1.4. Build the package by using “colcon build” command.
Step 2: Write the “action_server” node in Python, wrapped in the action_server package. See an example package here: https://github.com/ros2/examples/tree/foxy/rclpy/actions/minimal_action_server
2.1. Make a Python package called “action_server_python”.
2.2. Check package.xml. Modify if necessary, particularly regarding the dependency packages.
2.3. Modify setup.py. Add the following:
1 2 3 4 5 |
entry_points={ 'console_scripts': [ 'server = ' + package_name + '.my_action_server:main', ], }, |
2.4. Write the action_server node in a file namely “my_action_server.py”, in the package subfolder inside the action_server package folder.
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 my_package.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 |
2.5. Build the package by using “colcon build” command.
Step 3: Write the “action_client” node in Python, wrapped in the action_client package. See an example package here: https://github.com/ros2/examples/tree/foxy/rclpy/actions/minimal_action_client
3.1. Make a Python package called “action_client_python”.
3.2. Modify package.xml. Modify if necessary, particularly regarding the dependency packages.
3.3. Modify setup.py. Add the following:
1 2 3 4 5 |
entry_points={ 'console_scripts': [ 'client = ' + package_name + '.my_action_client:main', ], }, |
3.4. Write the action_client node in a file namely “my_action_client.py”, in the package subfolder inside the action_client package folder.
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import rclpy from rclpy.action import ActionClient from rclpy.node import Node from my_package.action import Fibonacci class FibonacciActionClient(Node): def __init__(self): super().__init__('fibonacci_action_client') self._action_client = ActionClient(self, Fibonacci, 'fibonacci') def send_goal(self, order): goal_msg = Fibonacci.Goal() goal_msg.order = order self._action_client.wait_for_server() self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback) self._send_goal_future.add_done_callback(self.goal_response_callback) def goal_response_callback(self, future): goal_handle = future.result() if not goal_handle.accepted: self.get_logger().info('Goal rejected :(') return self.get_logger().info('Goal accepted :)') self._get_result_future = goal_handle.get_result_async() self._get_result_future.add_done_callback(self.get_result_callback) def get_result_callback(self, future): result = future.result().result self.get_logger().info('Result: {0}'.format(result.sequence)) rclpy.shutdown() def feedback_callback(self, feedback_msg): feedback = feedback_msg.feedback self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence)) def main(args=None): rclpy.init(args=args) action_client = FibonacciActionClient() action_client.send_goal(10) rclpy.spin(action_client) if __name__ == '__main__': main() |
3.5. Build the package by using “colcon build” command.
Example of action_server and action_client nodes in Python can be found here: https://docs.ros.org/en/foxy/Tutorials/Actions/Writing-a-Py-Action-Server-Client.html
Running the action
Type the following in the terminal to run the “action_server” node:
1 |
ros2 run action_server_python my_action_server.py |
Type the following in the terminal to run the “action_client” node:
1 |
ros2 run action_client_python my_action_client.py |
To show the stream of the action feedback:
1 |
ros2 topic echo /action_name/feedback |
Official documentation: