Actions
Actions enable long-running tasks with progress feedback and cancellation support, perfect for operations that take seconds or minutes to complete. Unlike services that return immediately, actions provide streaming feedback while executing complex workflows.
Use actions for robot navigation, trajectory execution, or any operation where you need progress updates and the ability to cancel mid-execution. Use services for quick request-response operations.
Action Lifecycle
stateDiagram-v2
[*] --> Idle
Idle --> Accepted: Send Goal
Accepted --> Executing: Start Processing
Executing --> Executing: Send Feedback
Executing --> Succeeded: Complete
Executing --> Canceled: Cancel Request
Executing --> Aborted: Error Occurs
Succeeded --> [*]
Canceled --> [*]
Aborted --> [*]
Components
| Component | Type | Purpose |
|---|---|---|
| Goal | Input | Defines the desired outcome |
| Feedback | Stream | Progress updates during execution |
| Result | Output | Final outcome when complete |
| Status | State | Current execution state |
Communication Pattern
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Send Goal
S->>C: Goal Accepted
loop During Execution
S->>C: Feedback Update
end
alt Success
S->>C: Result (Success)
else Canceled
C->>S: Cancel Request
S->>C: Result (Canceled)
else Error
S->>C: Result (Aborted)
end
Use Cases
Robot Navigation:
- Goal: Target position and orientation
- Feedback: Current position, distance remaining, obstacles detected
- Result: Final position, success/failure reason
Gripper Control:
- Goal: Desired grip force and position
- Feedback: Current force, contact detection
- Result: Grip achieved, object secured
Long Computations:
- Goal: Computation parameters
- Feedback: Progress percentage, intermediate results
- Result: Final computed value, execution time
Actions excel when operations take more than a few seconds and users need visibility into progress. For sub-second operations, prefer services for simplicity.
Action Server Example
This example demonstrates an action server that computes Fibonacci sequences. The server accepts goals, publishes periodic feedback with partial results, and supports cancellation.
/// Fibonacci action server node that computes Fibonacci sequences
///
/// # Arguments
/// * `ctx` - The ROS-Z context
/// * `timeout` - Optional timeout duration. If None, runs until ctrl+c.
pub async fn run_fibonacci_action_server(ctx: ZContext, timeout: Option<Duration>) -> Result<()> {
// Create a node named "fibonacci_action_server"
let node = ctx.create_node("fibonacci_action_server").build()?;
// Create an action server
// Note: The server variable must be kept alive for the duration of the function
// to ensure the action server and its background tasks remain active
let _server = node
.create_action_server::<Fibonacci>("fibonacci")
.build()?
.with_handler(|executing: ExecutingGoal<Fibonacci>| async move {
let order = executing.goal.order;
let mut sequence = vec![0, 1];
println!("Executing Fibonacci goal with order {}", order);
let mut canceled = false;
let mut cancel_sequence = None;
for i in 2..=order {
// Check for cancellation
if executing.is_cancel_requested() {
println!("Goal canceled!");
canceled = true;
cancel_sequence = Some(sequence.clone());
break;
}
let next = sequence[i as usize - 1] + sequence[i as usize - 2];
sequence.push(next);
// Publish feedback
executing
.publish_feedback(FibonacciFeedback {
partial_sequence: sequence.clone(),
})
.expect("Failed to publish feedback");
tokio::time::sleep(Duration::from_millis(500)).await;
}
if canceled {
executing
.canceled(FibonacciResult {
sequence: cancel_sequence.unwrap(),
})
.unwrap();
} else {
println!("Goal succeeded!");
executing.succeed(FibonacciResult { sequence }).unwrap();
}
});
println!("Fibonacci action server started");
if let Some(timeout) = timeout {
// For testing: run for the specified timeout
tokio::time::sleep(timeout).await;
} else {
tokio::signal::ctrl_c().await?;
}
Ok(())
}
Key points:
- Handler Pattern: Uses
.with_handler()to define asynchronous goal execution - Feedback Publishing: Sends partial results periodically via
publish_feedback() - Cancellation Support: Checks
is_cancel_requested()and handles graceful cancellation - Completion: Uses
.succeed()or.canceled()to send final result
Running the server:
# Start Zenoh router first
cargo run --example zenoh_router
# Run the server (runs until Ctrl+C)
cargo run --example demo_nodes_fibonacci_action_server
Action Client Example
This example demonstrates an action client that sends goals and monitors execution progress with feedback updates.
/// Fibonacci action client node that sends goals to compute Fibonacci sequences
///
/// # Arguments
/// * `ctx` - The ROS-Z context
/// * `order` - The order of the Fibonacci sequence to compute
pub async fn run_fibonacci_action_client(ctx: ZContext, order: i32) -> Result<Vec<i32>> {
// Create a node named "fibonacci_action_client"
let node = ctx.create_node("fibonacci_action_client").build()?;
// Create an action client
let client = node
.create_action_client::<Fibonacci>("fibonacci")
.build()?;
// Wait a bit for the server to be discovered
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
println!(
"Fibonacci action client started, sending goal with order {}",
order
);
// Send the goal
let mut goal_handle = client.send_goal(FibonacciGoal { order }).await?;
println!("Goal sent and accepted!");
// Set up feedback monitoring
if let Some(mut feedback_stream) = goal_handle.feedback() {
tokio::spawn(async move {
while let Some(fb) = feedback_stream.recv().await {
println!("Feedback: {:?}", fb.partial_sequence);
}
});
}
// Wait for the result with timeout
println!("Waiting for result (timeout: 10s)...");
let result = match tokio::time::timeout(
tokio::time::Duration::from_secs(10),
goal_handle.result(),
)
.await
{
Ok(Ok(result)) => {
println!("Final result: {:?}", result.sequence);
result
}
Ok(Err(e)) => {
eprintln!("Action failed: {}", e);
return Err(e);
}
Err(_) => {
eprintln!("Timeout waiting for action result");
return Err(zenoh::Error::from("Timeout waiting for action result"));
}
};
Ok(result.sequence)
}
Key points:
- Goal Sending: Uses
send_goal()to submit goals and get a handle - Feedback Monitoring: Spawns async task to receive and display feedback
- Result Handling: Waits for completion with timeout and error handling
- Type Safety: Strongly-typed goal, feedback, and result messages
Running the client:
# Basic usage - compute Fibonacci(10)
cargo run --example demo_nodes_fibonacci_action_client
# Compute Fibonacci(15)
cargo run --example demo_nodes_fibonacci_action_client -- --order 15
# Connect to specific router
cargo run --example demo_nodes_fibonacci_action_client -- --endpoint tcp/localhost:7447
Complete Action Workflow
Terminal 1 - Start Zenoh Router:
cargo run --example zenoh_router
Terminal 2 - Start Action Server:
cargo run --example demo_nodes_fibonacci_action_server
Terminal 3 - Send Goals from Client:
cargo run --example demo_nodes_fibonacci_action_client -- --order 10
You'll see:
- Client: Goal sent, feedback updates with partial sequences, final result
- Server: Goal received, executing with feedback, completion status
Always implement timeout mechanisms for action clients. Long-running actions can fail or hang, and clients need graceful degradation strategies.
Comparison with Other Patterns
| Pattern | Duration | Feedback | Cancellation | Use Case |
|---|---|---|---|---|
| Pub-Sub | Continuous | No | N/A | Sensor data streaming |
| Service | < 1 second | No | No | Quick queries |
| Action | Seconds to minutes | Yes | Yes | Long-running tasks |
Resources
- ROS 2 Actions Documentation - Official ROS 2 action guide
- ros-z Examples - Working action implementations
- Services - Simpler request-response pattern
Action implementation is evolving. Check the ros-z repository for the latest examples and API updates.