Actions: Long-Running Tasks with Feedback
Overview
Actions extend the request-response pattern of services by adding support for long-running tasks, periodic feedback, and cancellation. In this chapter, you'll learn to implement actions for navigation, manipulation, and other tasks that require time to complete.
Duration: 6 hours Week: 4-5 Prerequisites: Understanding of services and asynchronous programming
Learning Objectives
By the end of this chapter, you will be able to:
- Explain when to use actions instead of topics or services
- Create action servers that execute long-running tasks
- Implement action clients that send goals and receive feedback
- Handle goal acceptance, feedback publishing, and result delivery
- Implement goal cancellation and preemption
- Define custom action (.action) files for specific robotics tasks
Chapter Contents
1. Why Actions?
Content to be added: Comparison with topics and services, use cases (navigation, manipulation, trajectory execution).
2. Action Architecture
Content to be added: Goal-feedback-result pattern, action server/client model, state machine.
3. Action Components
Content to be added: Goal, feedback, and result messages; action state transitions.
4. Creating Action Servers
Content to be added: Python and C++ server implementations, goal handling, feedback publishing.
5. Creating Action Clients
Content to be added: Sending goals, receiving feedback, handling results, cancellation.
6. Custom Actions
Content to be added: Defining .action files, message structure, building custom actions.
Hands-On Labs
Lab 5.1: Fibonacci Action Server
Lab instructions to be added: Implement an action server that computes Fibonacci sequences with progress feedback.
Lab 5.2: Navigation Action Client
Lab instructions to be added: Create an action client for robot navigation with waypoint feedback.
Code Examples
Python Action Server Example
# Dependencies: rclpy>=3.3.0, action_tutorials_interfaces
import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
from action_tutorials_interfaces.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...')
# Create feedback message
feedback_msg = Fibonacci.Feedback()
feedback_msg.sequence = [0, 1]
# Execute the Fibonacci sequence
for i in range(1, goal_handle.request.order):
feedback_msg.sequence.append(
feedback_msg.sequence[i] + feedback_msg.sequence[i-1]
)
self.get_logger().info(f'Feedback: {feedback_msg.sequence}')
goal_handle.publish_feedback(feedback_msg)
goal_handle.succeed()
# Create result message
result = Fibonacci.Result()
result.sequence = feedback_msg.sequence
return result
def main(args=None):
rclpy.init(args=args)
fibonacci_action_server = FibonacciActionServer()
rclpy.spin(fibonacci_action_server)
Python Action Client Example
# Dependencies: rclpy>=3.3.0, action_tutorials_interfaces
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from action_tutorials_interfaces.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 feedback_callback(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info(f'Received feedback: {feedback.sequence}')
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}')
Additional code examples to be added.
Robotics Use Cases
Content to be added: Navigation (move_base), manipulation (MoveIt), autonomous charging, multi-step task execution.
Goal Lifecycle and States
Content to be added: State diagram showing PENDING, ACTIVE, SUCCEEDED, CANCELED, ABORTED states.
Assessment
Practical Project: Implement a pick-and-place action server with:
- Goal: Target object pose
- Feedback: Current gripper position and task stage
- Result: Success/failure with error diagnostics
- Cancellation support during approach phase
Success Criteria:
- Publishes feedback at 5 Hz minimum
- Handles cancellation gracefully
- Returns detailed error messages on failure
Troubleshooting
Common action-related issues to be added: goal timeouts, feedback delays, cancellation race conditions.
Resources
Next Chapter: Multi-Node Systems - Integrate all communication patterns into complex robotic systems