When a topic isn't the right tool
Think back to Lesson 1's three styles. A topic is a firehose — data flows whether or not anyone is listening. But some interactions are a back-and-forth:
- "Add these two numbers." → "The answer is 7."
- "Reset the odometry." → "Done."
- "Are you calibrated?" → "Yes."
Each is one request and one response. That's a service. The node offering the answer is the server; the node asking is the client.
A service uses a two-part type
Where a topic has a message type, a service has a type split by --- into a request part and a response part. ROS 2 ships an example, example_interfaces/srv/AddTwoInts:
ros2 interface show example_interfaces/srv/AddTwoInts
# int64 a <- request
# int64 b <- request
# ---
# int64 sum <- response
Write a service server
~/ros2_ws/src/my_robot/my_robot/add_server.py
import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
class AddServer(Node):
def __init__(self):
super().__init__('add_server')
# create_service(type, name, callback)
self.srv = self.create_service(
AddTwoInts, 'add_two_ints', self.handle_request)
self.get_logger().info('add_two_ints service is ready')
def handle_request(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(
f'{request.a} + {request.b} = {response.sum}')
return response # this is sent back to the client
def main(args=None):
rclpy.init(args=args)
rclpy.spin(AddServer())
rclpy.shutdown()
if __name__ == '__main__':
main()
Register it in setup.py ('add_server = my_robot.add_server:main'), add <depend>example_interfaces</depend> to package.xml, then colcon build and source install/setup.bash.
Call the service — no client code needed yet
Run the server, then call it straight from the command line in another terminal:
Terminal 1
ros2 run my_robot add_server
Terminal 2
ros2 service list
# /add_two_ints
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5, b: 2}"
# response: example_interfaces.srv.AddTwoInts_Response(sum=7)
The server received your request, computed the sum, and sent back a response — a complete request/response round trip.
Change the server so it multiplies instead of adds. Rebuild, then call it with {a: 6, b: 7}. What comes back? (You only need to change one line in handle_request.)
Parameters — settings you can change at launch
Hard-coding values like a robot's top speed is a bad idea — you'd have to edit and rebuild to change them. Parameters let a node declare named settings that can be set from the outside.
Here's a tiny node that declares a max_speed parameter and reads it:
~/ros2_ws/src/my_robot/my_robot/speed_config.py
import rclpy
from rclpy.node import Node
class SpeedConfig(Node):
def __init__(self):
super().__init__('speed_config')
# declare_parameter(name, default_value)
self.declare_parameter('max_speed', 1.0)
speed = self.get_parameter('max_speed').value
self.get_logger().info(f'max_speed is set to {speed} m/s')
def main(args=None):
rclpy.init(args=args)
rclpy.spin(SpeedConfig())
rclpy.shutdown()
if __name__ == '__main__':
main()
Register and build it, then override the default at launch — no code change, no rebuild:
# Uses the default of 1.0
ros2 run my_robot speed_config
# Override it on the command line
ros2 run my_robot speed_config --ros-args -p max_speed:=2.5
# [speed_config]: max_speed is set to 2.5 m/s
While a node runs, you can list, read, and even set its parameters from another terminal:
ros2 param list
ros2 param get /speed_config max_speed
ros2 param set /speed_config max_speed 3.0
Topics vs. services vs. parameters — a recap
- Topic — continuous one-to-many stream. Sensor data, commands. (Lesson 4)
- Service — one request, one response, on demand. Trigger an action, ask a question.
- Parameter — a named setting that configures a node, set at launch or live.
Key takeaways
- Services are for request/response: a server answers, a client asks.
- Service types have a request and response half separated by
---. - You can call any service straight from the CLI with
ros2 service call. - Parameters let you configure a node with
--ros-args -p name:=value— no rebuild needed.