CodeTheRobot

ROS 2 Lyrical Luth · Lesson 09 of 86

Services: request and response

Service servers and clients, .srv files, and when not to use a service.

Estimated time · 60–90 min

Learning goals

What you will understand

  • Explain services: request and response in your own words
  • Use it in a small robotics example
  • Verify the result instead of assuming it worked

Understand the idea first

The mental model

A service is a short, synchronous-style request and response, like a function call between nodes. The type is defined in a .srv file with the request fields, a line of three dashes, and the response fields. It fits quick queries and commands such as 'reset the odometer'.

A service call should return quickly. For work that takes seconds and needs progress or cancelling, use an action. Always check the server exists with wait_for_service before calling, and never block a callback waiting for another service.

Guided lesson

Work through these steps

  1. 1.

    Source the ROS 2 Lyrical environment in a fresh terminal.

  2. 2.

    Read the services: request and response example and identify what each line does.

  3. 3.

    Build only the affected package, source the workspace overlay, and run the example.

  4. 4.

    Inspect the result from a second terminal with the ros2 CLI or RViz2 and compare it with the expected result.

Worked example

Read it before you run it

srv_ = create_service<example_interfaces::srv::AddTwoInts>("add",
// Offer a service named /add.
  [](const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> req,
// The callback receives the request...
     std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> res) {
// ...and a response object to fill in.
    res->sum = req->a + req->b;
// Compute the answer.
  });
// End of the callback.
client_ = create_client<example_interfaces::srv::AddTwoInts>("add");
// In a client node: create a client for /add.
if (!client_->wait_for_service(std::chrono::seconds(2))) return;
// Give up if the server is not running.
auto future = client_->async_send_request(request);
// Send the request without blocking; a future holds the answer.

What the example is doing

  1. 1.The ros2 commands query the running graph; they do not prove that a node's internal behavior is correct.
  2. 2.Build from the workspace root, then source install/setup.bash so the shell can discover the new package.
  3. 3.Use a second sourced terminal to inspect nodes and interfaces while the example runs.

Watch it run · Messages between nodes

See what this code does

The terminal types this lesson's practice solution and runs it. The scene shows the kind of robot behaviour that code drives. It plays by itself and loops.

requestresponseclientservercalls answered: 0MESSAGES BETWEEN NODES

Demo for Services: request and response. Expected result: sum=5

Practice code

Now type it yourself

Copy the starter into your ROS 2 package, replace every TODO, then build with colcon build and run it from a sourced terminal.

// robot_nodes/src/add_server.cpp
#include "example_interfaces/srv/add_two_ints.hpp"
#include "rclcpp/rclcpp.hpp"

using AddTwoInts = example_interfaces::srv::AddTwoInts;

class AddServer : public rclcpp::Node {
 public:
  AddServer() : Node("add_server") {
    // TODO: create service "add": set res->sum to req->a + req->b
  }

 private:
  rclcpp::Service<AddTwoInts>::SharedPtr srv_;
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<AddServer>());
  rclcpp::shutdown();
  return 0;
}
// Test: ros2 service call /add example_interfaces/srv/AddTwoInts "{a: 2, b: 3}"

Expected output

sum=5
Stuck? Show the full solution (try it yourself first)
// robot_nodes/src/add_server.cpp
#include "example_interfaces/srv/add_two_ints.hpp"
#include "rclcpp/rclcpp.hpp"

using AddTwoInts = example_interfaces::srv::AddTwoInts;

class AddServer : public rclcpp::Node {
 public:
  AddServer() : Node("add_server") {
    srv_ = create_service<AddTwoInts>(
        "add", [](const std::shared_ptr<AddTwoInts::Request> req,
                  std::shared_ptr<AddTwoInts::Response> res) { res->sum = req->a + req->b; });
  }

 private:
  rclcpp::Service<AddTwoInts>::SharedPtr srv_;
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<AddServer>());
  rclcpp::shutdown();
  return 0;
}
// Test: ros2 service call /add example_interfaces/srv/AddTwoInts "{a: 2, b: 3}"

Expected result

How you know it worked

  • ROS_DISTRO is lyrical in every terminal used for the exercise.
  • The complete package from the exercise builds and the expected graph entity appears in a second terminal.

Practical exercise

Apply the lesson

Write a ResetCounter service (no request fields, response bool success) for a counter node. Call it with ros2 service call, then write a C++ client and confirm wait_for_service times out when the server is stopped.

Before you continue

  • ROS_DISTRO reports lyrical in every terminal you used
  • The example builds and runs without errors
  • You checked the result with a ros2 command, RViz2 or a test, not just by reading the code

If something goes wrong

ros2 cannot find a package or executable.

Source /opt/ros/lyrical/setup.bash, build from the workspace root, then source install/setup.bash in that terminal.

Nodes run but cannot discover each other.

Confirm both terminals use Lyrical, the same ROS_DOMAIN_ID, and compatible network/discovery settings.