Lecture 19: ROS Actions
Majid Khonji
202
1
www.avlab.io
Khalifa University
ROBO
Quick ROS graph recap
Rule of thumb: Topics = continuous data; Services = quick questions; Actions = long tasks.
2
ku.ac.ae
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
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
Action Architecture Overview
5
ku.ac.ae
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
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
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
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
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
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
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
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
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
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
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
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
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.45 → 1.23 → 0.01 → Result!
18
ku.ac.ae
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
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
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
Custom Topic Messages
22
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
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
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
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