ROS 2 Foundations · Lesson 5 of 6

Lesson 05

Services & Parameters

Topics are perfect for constant streams, but sometimes you want to ask a question and wait for one answer — that's a service. And sometimes you want to change how a node behaves without editing its code — that's a parameter. This lesson covers both.

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:

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.

Try it yourself

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
Inspect & change parameters live

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

Key takeaways