Chapter 3: Services, Actions, and Parameters
In Chapter 2, you learned about topics—the asynchronous, many-to-many communication pattern ideal for streaming sensor data. But not all robot communication fits the publish-subscribe model. What if you need:
- A one-time computation (e.g., inverse kinematics for an arm)? → Use services (synchronous request-response)
- A long-running task with progress updates (e.g., navigate to a waypoint)? → Use actions (asynchronous with feedback)
- Configurable behavior (e.g., adjust PID gains at runtime)? → Use parameters (dynamic configuration)
This chapter teaches you these advanced communication primitives and shows how to integrate AI decision logic with ROS 2 control systems.
3.1 Services: Request-Response Communication
3.1.1 Service Definitions
ROS 2 services use .srv files defining request and response types:
# example_interfaces/srv/AddTwoInts.srv
int64 a
int64 b
---
int64 sum
The --- separator divides request (top) from response (bottom). Standard service types include:
std_srvs/srv/Trigger: No request data, bool response + messagestd_srvs/srv/SetBool: Bool request, bool response + messageexample_interfaces/srv/AddTwoInts: Two integers → sum
3.1.2 Creating a Service Server
A service server listens for requests and executes a callback function:
1import rclpy2from rclpy.node import Node3from example_interfaces.srv import AddTwoInts45class AddTwoIntsServer(Node):6 def __init__(self):7 super().__init__('add_two_ints_server')89 # Create service: service type, service name, callback function10 self.srv = self.create_service(11 AddTwoInts,12 'add_two_ints',13 self.add_two_ints_callback14 )1516 self.get_logger().info('Add Two Ints Server started')1718 def add_two_ints_callback(self, request, response):19 """Service callback: receives request, populates response"""20 response.sum = request.a + request.b21 self.get_logger().info(22 f'Incoming request: a={request.a}, b={request.b}'23 )24 self.get_logger().info(f'Sending response: sum={response.sum}')25 return response2627def main(args=None):28 rclpy.init(args=args)29 node = AddTwoIntsServer()30 rclpy.spin(node)31 node.destroy_node()32 rclpy.shutdown()3334if __name__ == '__main__':35 main()
Service Server API:
self.srv = self.create_service(
srv_type=AddTwoInts, # Service type (from .srv file)
srv_name='add_two_ints', # Service name (string)
callback=self.callback_function # Function to handle requests
)
Callback Signature:
def callback(self, request, response):
# request: object with fields from request part of .srv
# response: object with fields from response part of .srv
response.field1 = compute_value()
return response # MUST return response object
3.1.3 Creating a Service Client
A service client sends requests and waits for responses:
1import sys2import rclpy3from rclpy.node import Node4from example_interfaces.srv import AddTwoInts56class AddTwoIntsClient(Node):7 def __init__(self):8 super().__init__('add_two_ints_client')910 # Create client: service type, service name11 self.client = self.create_client(AddTwoInts, 'add_two_ints')1213 # Wait for service to be available (timeout after 5 seconds)14 while not self.client.wait_for_service(timeout_sec=5.0):15 self.get_logger().info('Service not available, waiting...')1617 self.req = AddTwoInts.Request()1819 def send_request(self, a, b):20 """Send async request and return future"""21 self.req.a = a22 self.req.b = b23 self.future = self.client.call_async(self.req)24 return self.future2526def main(args=None):27 rclpy.init(args=args)2829 if len(sys.argv) != 3:30 print('Usage: python3 add_two_ints_client.py <a> <b>')31 return3233 client_node = AddTwoIntsClient()34 future = client_node.send_request(int(sys.argv[1]), int(sys.argv[2]))3536 # Spin until response received37 rclpy.spin_until_future_complete(client_node, future)3839 if future.result() is not None:40 response = future.result()41 client_node.get_logger().info(42 f'Result: {sys.argv[1]} + {sys.argv[2]} = {response.sum}'43 )44 else:45 client_node.get_logger().error('Service call failed')4647 client_node.destroy_node()48 rclpy.shutdown()4950if __name__ == '__main__':51 main()
Service Client API:
# 1. Create client
self.client = self.create_client(AddTwoInts, 'add_two_ints')
# 2. Wait for service availability
self.client.wait_for_service(timeout_sec=5.0)
# 3. Create request object
request = AddTwoInts.Request()
request.a = 10
request.b = 20
# 4. Call service asynchronously
future = self.client.call_async(request)
# 5. Wait for response
rclpy.spin_until_future_complete(node, future)
response = future.result()
3.1.4 Service Communication Flow
sequenceDiagram
participant Client as Service Client<br/>(AddTwoIntsClient)
participant Server as Service Server<br/>(AddTwoIntsServer)
Client->>Client: create_client()
Client->>Server: wait_for_service()
Note over Server: Server already running
Client->>Server: call_async(Request{a=5, b=3})
Note over Client: Non-blocking call,<br/>returns Future
activate Server
Server->>Server: add_two_ints_callback()
Server->>Server: Compute: 5 + 3 = 8
Server->>Client: Response{sum=8}
deactivate Server
Client->>Client: future.result() = 8
Note over Client: Blocking wait for Future
3.2 Actions: Long-Running Tasks with Feedback
Services are great for quick computations, but what about tasks that take seconds or minutes? Actions extend the service pattern with:
- Feedback: Server sends progress updates during execution
- Cancellation: Client can cancel the goal mid-execution
- Result: Final outcome when task completes