1 of 27

Lecture 19: ROS Actions

Majid Khonji

202

1

www.avlab.io

Khalifa University

ROBO

2 of 27

Quick ROS graph recap

  • Nodes: processes that do work
  • Topics: pub/sub stream of data (one-way, many-to-many)
  • Services: request/response, synchronous by design
  • Actions: long-running tasks with feedback and cancellation
  • Parameters: node configuration

Rule of thumb: Topics = continuous data; Services = quick questions; Actions = long tasks.

2

ku.ac.ae

3 of 27

What are ROS2 Actions?

- Actions are a communication pattern for long-running tasks

- Built on top of topics and services

- Provide three key features:

- Goal: Send a request to start a task

- Feedback: Receive periodic updates on progress

- Result: Get the final outcome when complete

- Support preemption (cancellation) of ongoing tasks

3

ku.ac.ae

4 of 27

Examples

- Navigation: Move a robot to a target location

- Goal: Target pose

- Feedback: Current distance remaining

- Result: Success or failure

- Manipulation: Pick and place objects

- Goal: Object to grasp

- Feedback: Current gripper position

- Result: Object grasped successfully

4

ku.ac.ae

5 of 27

Action Architecture Overview

5

ku.ac.ae

6 of 27

Action Components

Actions use 2 topics and 3 services under the hood:

Topics:

- _action/status - Current status of goals

- _action/feedback - Periodic progress updates

Services:

- _action/send_goal - Send goal to action server

- _action/cancel_goal - Request goal cancellation

- _action/get_result - Retrieve final result

6

ku.ac.ae

7 of 27

Action States

Active States:

- ACCEPTED (status: 2) - Goal accepted, awaiting execution

- EXECUTING (status: 3) - Currently being processed

- CANCELING (status: 5) - Cancel requested, cleaning up

Terminal States:

- SUCCEEDED (status: 4) - Completed successfully

- ABORTED (status: 6) - Failed during execution

- CANCELED (status: 7) - Cancelled by client

How to observe states:

ros2 topic echo /action_name/_action/status

7

ku.ac.ae

8 of 27

Example: Fibonacci Action

File: action/Fibonacci.action

This action computes Fibonacci sequence up to order n.

# Request

int32 order

---

# Result

int32[] sequence

---

# Feedback

int32[] partial_sequence

8

ku.ac.ae

9 of 27

Recall Field types

Primitive types:

- Integers: `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`

- Floats: `float32`, `float64`

- Others: `bool`, `string`, `byte`

Arrays:

- Fixed size: `float64[3] position`

- Unbounded: `string[] names`

- Bounded: `float64[<=10] sensor_data`

Nested types:

- Use message types: `geometry_msgs/Point target`

Default values:

float64 timeout=5.0

bool verbose=false

9

ku.ac.ae

10 of 27

Setting up Action Package

In package.xml:

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

In CMakeLists.txt:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}

"action/Fibonacci.action"

)

10

In your terminal

colcon build --packages-select my_robot_actions

source install/setup.bash

ros2 interface show my_robot_actions/action/RotateAbsolute

ku.ac.ae

11 of 27

Creating an Action Server (Part 1)

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)

11

ku.ac.ae

12 of 27

Creating an Action Server (Part 2)

def execute_callback(self, goal_handle):

self.get_logger().info('Executing goal...')

# Initialize feedback

feedback_msg = Fibonacci.Feedback()

feedback_msg.partial_sequence = [0, 1]

# Compute Fibonacci sequence

for i in range(1, goal_handle.request.order):

# Check if cancelled

if goal_handle.is_cancel_requested:

goal_handle.canceled()

return Fibonacci.Result()

# Calculate next Fibonacci number

feedback_msg.partial_sequence.append(

feedback_msg.partial_sequence[i] +

feedback_msg.partial_sequence[i-1])

# Publish feedback

goal_handle.publish_feedback(feedback_msg)

time.sleep(1)

# Set success

goal_handle.succeed()

# Return result

result = Fibonacci.Result()

result.sequence = feedback_msg.partial_sequence

return result

# Request

int32 order

---

# Result

int32[] sequence

---

# Feedback

int32[] partial_sequence

12

ku.ac.ae

13 of 27

Creating an Action Client (Part 1)

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

# Wait for server

self._action_client.wait_for_server()

# Send goal

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)

13

ku.ac.ae

14 of 27

Creating an Action Client (Part 2)

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(f'Result: {result.sequence}')

rclpy.shutdown()

def feedback_callback(self, feedback_msg):

feedback = feedback_msg.feedback

self.get_logger().info(f'Feedback: {feedback.partial_sequence}')

14

ku.ac.ae

15 of 27

Cancelling Goals

From Client:

# Store goal handle when sending goal

self._goal_handle = goal_handle

# Later, to cancel:

cancel_future = self._goal_handle.cancel_goal_async()

cancel_future.add_done_callback(self.cancel_done_callback)

15

In Server:

if goal_handle.is_cancel_requested:

goal_handle.canceled()

return Fibonacci.Result()

ku.ac.ae

16 of 27

Testing Actions with CLI

List available actions:

ros2 action list

Get action info:

ros2 action info /fibonacci

Send a goal:

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

Send goal with feedback:

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

# Request

int32 order

---

# Result

int32[] sequence

---

# Feedback

int32[] partial_sequence

16

ku.ac.ae

17 of 27

Hands-On: TurtleSim Actions

Setup - Launch TurtleSim:

ros2 run turtlesim turtlesim_node

Discover & Inspect:

ros2 action list -t # List actions

ros2 node info /turtlesim # See action servers

ros2 interface show turtlesim/action/RotateAbsolute # View interface

Key Interface:

- Goal: float32 theta (target angle in radians)

- Result: float32 delta (remaining rotation)

- Feedback: float32 remaining (current remaining)

17

ku.ac.ae

18 of 27

TurtleSim: Send Goals & Observe2

Basic Goal (90°):

ros2 action send_goal /turtle1/rotate_absolute \

turtlesim/action/RotateAbsolute "{theta: 1.57}"

Goal with Feedback (180°):

ros2 action send_goal /turtle1/rotate_absolute \

turtlesim/action/RotateAbsolute "{theta: 3.14}" --feedback

Watch feedback stream: remaining: 2.451.230.01 → Result!

18

ku.ac.ae

19 of 27

TurtleSim: Monitor Topics & Cancel

Monitor underlying topics:

ros2 topic echo /turtle1/rotate_absolute/_action/status

ros2 topic echo /turtle1/rotate_absolute/_action/feedback

Try cancellation:

# Send long rotation (2 full circles)

ros2 action send_goal /turtle1/rotate_absolute \

turtlesim/action/RotateAbsolute "{theta: 12.56}" --feedback

# Press Ctrl+C while rotating, Turtle stops!

19

ku.ac.ae

20 of 27

Best Practices

1. Always check for cancellation in long loops

2. Provide meaningful feedback at regular intervals

3. Set appropriate timeouts for action clients

4. Handle edge cases (invalid goals, server unavailable)

5. Use descriptive names for goals, feedback, and results

6. Log important events (goal accepted, cancelled, failed)

7. Document expected behavior in action definition comments

20

ku.ac.ae

21 of 27

Action vs Service: When to Use What?

Use Actions when:

- Task takes more than ~1 second

- Need progress updates

- User might want to cancel

- Examples: navigation, manipulation, long computations

Use Services when:

- Quick request-response (<1 second)

- No need for progress updates

- Cancellation not needed

- Examples: sensors readings, simple calculations

21

ku.ac.ae

22 of 27

Custom Topic Messages

22

23 of 27

Custom Topic Messages

Why? Bundle robot-specific data together with type safety.

Package: my_robot_msgs/ with msg/ folder, CMakeLists.txt, package.xml

Data Types:

- Primitives: bool, int32, float32, string

- Arrays: float32[10] (fixed), float32[] (variable)

- Nested messages: Use other ROS messages inside yours!

- Constants: uint8 MAX_VALUE=100

23

ku.ac.ae

24 of 27

Custom Message Example

msg/SensorData.msg - Messages inside messages:

std_msgs/Header header # Built-in ROS message with timestamp

string sensor_name

float32 temperature

float32 humidity

uint8 STATUS_OK=0

uint8 status

msg/RobotState.msg - Your own messages inside:

std_msgs/Header header

geometry_msgs/Pose pose # Position + orientation

SensorData[] sensors # Array of YOUR custom message!

string robot_mode

Key idea: Build complex structures from simple parts!

24

ku.ac.ae

25 of 27

Build & Use Custom Messages

Setup (same as actions):

<!-- package.xml -->

<buildtool_depend>ament_cmake</buildtool_depend>

<build_depend>rosidl_default_generators</build_depend>

<build_depend>std_msgs</build_depend>

<build_depend>geometry_msgs</build_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

CMakeLists.txt

rosidl_generate_interfaces(${PROJECT_NAME}

"msg/SensorData.msg"

"msg/RobotState.msg"

DEPENDENCIES std_msgs geometry_msgs

)

25

ku.ac.ae

26 of 27

Using Nested Messages in Python

from my_robot_msgs.msg import SensorData, RobotState

from geometry_msgs.msg import Pose

# Create nested message

msg = RobotState()

msg.header.stamp = self.get_clock().now().to_msg()

msg.robot_mode = "autonomous"

# Set pose (message inside message)

msg.pose.position.x = 1.0

msg.pose.position.y = 2.0

# Add array of custom messages (messages inside message!)

sensor1 = SensorData()

sensor1.sensor_name = "temp1"

sensor1.temperature = 23.5

msg.sensors.append(sensor1)

self.publisher.publish(msg)

Access nested data: msg.pose.position.x or msg.sensors[0].temperature

26

ku.ac.ae

27 of 27

Reference

27

ku.ac.ae