Creating ROS 1 Services

Make discrete requests between ROS nodes for one-off tasks
José L. MillánJosé L. Millán ·
10 min read
Published
Creating ROS 1 Services

In our previous ROS 1 basics tutorial, we covered how ROS nodes communicate with each other by publishing and subscribing to message streams on topics.

Another common way nodes can share information with each other is via services. With ROS 1 services, one or many "client" nodes can make requests to a "server" node and wait for its response. These make services great for performing on-demand tasks – like performing on-the-fly computations or simple one-time tasks.

In this tutorial, we'll learn how to build a client and server node in C++, and implement a service for them to pass data to each other.

How do services work?

Unlike topics, which follow a publish-subscribe model to send each other information, services follow a request-response model.

With services, a client node must first call the server in order to send a request. This means that in the server-client mode, nodes do not use a communication stream until it’s absolutely needed, preventing unnecessary bandwidth usage. Then, the client node must wait for a response from the server (or until a timeout is reached). For this reason, services can confirm communication between nodes.

Services consist of two parts - a request and a response. Each part can be empty or contain as many data fields that you want to share between nodes.

To define a service, you must create a .srv file in your ROS 1 package’s srv directory that contains both those components:

# Request
---
# Response

Let’s start by exploring three different service definitions in the std_srvs package:

  • Empty.srv – Empty request and response. The only data that is passed from client to server is the call itself.
  • Trigger.srv – Empty request and a response with success and message fields. This allows the server to send more information to the client.
  • SetBool.srv – Request with data field and response with success and message fields.

The more fields a service contains, the more information the client and server can exchange with each other.

When should I use services?

Let’s suppose you want to create a routine check for your robot – for example, you may want to verify at start-up that your robot's servo motors are all working properly.

If you were to create a publisher for this task, your node could publish its check results before a corresponding subscriber becomes available. As a result, you'd miss the check results and be left in the dark about the status of your motors.

But if you were to create a service instead, your server node wouldn't broadcast any data before it is explicitly requested by a client node. Using a service ensures that you do not miss crucial information due to inopportune timing.

In the service scenario, the client node could be a diagnostics node that gathers robot state data at start-up. The server node could be a motor node that controls the robot’s servo motors. If all goes well, and the actuators are working properly, the server node would deliver a response saying that all checks passed. If any motors encountered an issue, however, the response would raise an alarm.

Service request and response The client will wait for a response within a specified timeout period.

A ROS 1 service in action

Now that we’ve discussed how when you might use ROS services, let’s dive into some actual code.

Let's use the Trigger.srv we saw before. Remember that this service definition contains no field in the request. The client is not sending any data to the server when it makes this request - it is just a trigger. The server, however, will respond with success and message fields.

We’ll begin by creating our own ROS 1 package. Navigate to the src folder of your ros1_ws and create a package named srv_client_checks:

$ cd ros1_ws/src/
$ catkin_create_pkg srv_client_checks roscpp std_srvs

Writing a server node

Let's start out with our server motor_node, which will run the necessary check on the servo motors.

Go to the src folder of your new package and create a file named motor_node.cpp:

// Include the ROS library and trigger service type
#include "ros/ros.h"
#include "std_srvs/srv/trigger.h"

Define two functions: moveMotorToMinAndMax and doChecks.

moveMotorToMinAndMax will simulate the motors moving to their maximum and minimum positions. If the motor successfully reaches both limits, the function will return true; otherwise, it will return false.

bool moveMotorToMinAndMax(int motor_id){
  bool reached_min = false;
  bool reached_max = false;

  // Add code here that moves the motor to its min and max positions

  if (reached_min && reached_max){
    return true;
  } else {
    return false;
  }
}

The doChecks server callback receives two inputs (a request and response) and will run when the server is activated by a client. It iterates over all available motors to perform the moveMotorToMinAndMax check on each one, then sends back a response:

// ROS helps pass the request and response between client and server
void doChecks(std_srvs::srv::Trigger::Request &request, std_srvs::srv::Trigger::Response &response){

  // Prepare response
  response.success = true;
  response.message = "";
  ROS_INFO("Received request to check motors...");

  // Iterate over all motors (e.g. 5) to perform the check
  for (int i = 0; i < 5; i++) {
    ROS_INFO("Checking motor %i",i);
    auto res = moveMotorToMinAndMax(i);
    // If something fails, change response `success` to false and add info to the `message`
    if (!res) {
      response.success = false;
      response.message += "\nMotor"+std::to_string(i)+" Failed! - ";
    }
  }

  ROS_INFO("Sending back response...");

  // Always return true if call succeeded
  return true;
}

Finally, we create a main function as our entrypoint:

int main(int argc, char **argv) {
  // Initiate the ROS1 library, passing the node name
  ros::init(argc, argv, "motor_node");

  // Create a NodeHandle
  ros::NodeHandle n;

  // Create the "checks" service with a doChecks callback
  ros::ServiceServer service = n.advertiseService("checks", doChecks);

  ROS_INFO("Ready to check motors");

  // Spin the node until it's terminated
  ros::spin(node);
  ros::shutdown();
  return 0;
}

Writing a client node

Now that we have a server node, let's move on to the client node that will request a response from it.

Create a diagnostics_node.cpp file in your package's src folder, and include the following libraries:

#include "ros/ros.h"
#include "std_srvs/srv/trigger.h"

Let's start with our main function:

int main(int argc, char **argv) {
  // Initiate the ROS library, passing the node name
  ros::init(argc, argv, "diagnostics_node");

  // Create a NodeHandle
  ros::NodeHandle n;

  // Create client inside the node to call our "checks" server node
  ros::ServiceClient client=n.serviceClient<std_srvs::Trigger>("checks");

Next, let’s create a request that the client can send to the server using the Trigger.srv service:

  // Create the request, which is empty
  std_srvs::Trigger srv_call;

Before we send the request, let's make sure the server is active – otherwise, our request will be lost. The function wait_for_service() requires a timeout, which is the maximum time a client will wait for a response.

  // Wait for the server for a maximum  of 5 seconds
  ros::service::waitForService("checks", ros::Duration(5));

Once the server is on, the program will continue and send a request:

if (client.call(srv_call)) {
  ROS_INFO("Server call successful! Response was %d: %s", srv_call.response.success, srv_call.response.message.c_str());
 } else {
   ROS_ERROR("Failed to call 'checks' service");
 }
 return 0;
}

If client.call(srv_call) returns true, we know that the server has responded correctly, and we can process that response. Otherwise, we'll get an error message telling us that we failed to call the "checks" service.

Compiling the package

Add the executables to your compilation file, and install them in your path, so that the rosrun command can find them.

In the CMakeList.txt file, make sure that you've the following:

# Tell compiler that we require the roscpp and std_srvs packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_srvs
)

# IMPORTANT: Executables must be defined after these lines
catkin_package(
  # INCLUDE_DIRS include
  # LIBRARIES srv_client_checks
  # CATKIN_DEPENDS roscpp std_srvs
  # DEPENDS system_lib
)

# Add the nodes as executables with their dependencies
add_executable(motor_node src/motor_node.cpp)
target_link_libraries(motor_node ${catkin_LIBRARIES})
add_executable(diagnostics_node src/diagnostics_node.cpp)
target_link_libraries(diagnostics_node ${catkin_LIBRARIES})

Return to the root folder of your workspace to compile with your new nodes:

$ cd ros1_ws/
$ catkin_make

Run your nodes

We can finally run both our client and server nodes to see if they can communicate with each other properly!

First, start up your ROS master in a terminal window:

$ roscore

You will need two other terminal windows – remember to source install/setup.bash in each one before running any ROS 1 command:

$ cd ros1_ws/
$ source install/setup.bash

The first terminal will contain the client diagnostics_node:

$ rosrun srv_client_checks diagnostics_node

This node will wait for motor_node to appear before requesting the checks.

Next, run the server motor_node to execute the checks:

$ rosrun srv_client_checks motor_node

When motor_node executes and the server activates, it performs its checks. In this case, a warning shows because we don't have any motors – there is no possible way for the checks to succeed!

Running both nodes Left: Client diagnostics_node. Right: Server motor_node.

You've now created a server-client communication that checks your actuators and sensors during start-up, making sure that everything is working properly before continuing.

Continue playing around with running your nodes to see how they behave. For example, what would you expect to happen if you run the server node before the client?

Keep learning!

We hope you enjoyed this tutorial and now have a better understand of how you can use ROS 1 services in your own robotics project. Continue learning with our ROS tutorials and other articles on our blog.

As always, feel free to check our docs or reach out to us directly in our Slack community to learn how our tools can help you accelerate your ROS development!


Read more:

Creating ROS 1 Actions
tutorial
ROS
Creating ROS 1 Actions

Coordinate open-ended communication between your ROS nodes

Esther WeonEsther WeonEsther Weon
José L. MillánJosé L. MillánJosé L. Millán
11 min read
Creating ROS 2 Actions
tutorial
ROS
Creating ROS 2 Actions

Coordinate open-ended communication between your ROS nodes

Esther WeonEsther WeonEsther Weon
11 min read

Get blog posts sent directly to your inbox.

Ready to try Foxglove?

Get started for free