Servicios
Qué son los servicios en ROS?? Ejemplo práctico para aclarar conceptos. Si queréis info que complemente este artículo, lo podréis hacer en el siguiente enlace

Qué son los servicios en ROS ??
En entradas anteriores de este blog hemos hablado del método principal de comunicación entre nodos, el método pub/sub. Si bien este método es muy sencillo, el nodo subscriptor y el nodo publicador hacen cada uno su tarea de modo independiente, es decir, supongamos que el nodo publicador se dedica a mandar la lectura de un sensor cada cierto tiempo y lo publica en un topic determinado, este envió de información (publicación), se hará independientemente de que haya o no nodos subscriptores.
Tal y como comentamos hace dos entradas de nuestro blog, podemos pensar en la analogía de la antena y la radio. La antena nuestro publisher emitirá independientemente de que alguien tenga o no la radio encendida. Esto hace que no haya manera de pasar información a la antena para por ejemplo, cambiar de canción.

Para ello está el segundo método de paso de información , el método de los servicios. El fundamento de este método es usado a menudo hoy en día en Internet, por ejemplo pensemos en la típica pagina para saber el tiempo que va a hacer hoy.
Esta página lo primero nos va a solicitar una ubicación, una vez que se la demos, nos mostrará el tiempo en ese lugar. Esto es un servicio, un método en el que yo paso una información, que se llama «request», y el servicio nos va a proveer de una respuesta llamada «response».
En definitiva, un servicio es un tipo de comunicación de ROS en el que pasamos un mensaje y nos devuelve una respuesta.
Un ejemplo de un servicio en una aplicación robótica, puede ser el de una aplicación en el que el usuario envía el tipo de pieza que se quiere localizar, request, y el servicio devuelve la localización de ese tipo de pieza, response.
En los servicios distinguimos dos partes bien diferenciadas, por un lado está el nodo cliente, que es el nodo en el cual se realiza la petición, el nodo que envía el “request”, y el nodo servidor, que es el que realiza la operación y devuelve una respuesta, devuelva la “response”.

En el caso de la analogía con una aplicación web para conocer el tiempo en un determinado lugar, el usuario mete en el nodo cliente la localización de donde quiere saber el tiempo, es decir manda la “request”, y el nodo servicio realiza las operaciones necesarias para devolver los datos del tiempo en ese lugar, devuelva la “response”.
Para que se produzca la comunicación entre nodos en los dos sentidos, del cliente al servicio y del servicio al cliente, ROS utiliza unos mensajes que irán incluidos en la carpeta srv de nuestro paquete y tendrán a su vez la terminación .srv.
Estos mensajes, imaginemos que tenemos un servicio en el que pasamos dos enteros y nos devuelve la suma de ellos, tendrán la siguiente estructura:
Int a //request
Int b
—
Int sum
La primera parte del mensaje, la que va antes de las tres rayas, pertenece al request y la siguiente parte a la response
Ejemplo práctico
Creación del mensaje
Ahora vamos a explicar como crear un servicio que sumará el valor de dos enteros, para ello crearemos dos archivos, uno que será el nodo servidor y otro que será el nodo cliente. Recordamos que este ejemplo lo tenéis en nuestro Github.
Pero antes de nada creamos un paquete dentro de nuestro workspace, si tienes dudas de como crear un workspace consulta esta entrada anterior.
Para crear el paquete, como también hemos visto en anteriores entradas, lo primero escribimos en la consola la ruta la carpeta src de nuestro workspace:
cd ~/catkin_ws/src
Ahora volvemos a escribir en la consola:
catkin_create_pkg test_servicios roscpp
Así creamos un paquete llamado test_servicios con dependencias a roscpp. Ahora navegamos a la ruta del paquete recién creado:
cd ~/catkin_ws/src/test_servicios
y creamos la carpeta srv
sudo mkdir srv
Dentro de esta carpeta crearemos un archivo, que será el mensaje que vamos a usar y le llamaremos mensaje_servicio.srv
Este archivo lo rellenaremos con el siguiente contenido:
Int a //request
Int b
---
Int sum // response
La parte de arriba del mensaje pertenece al request y la de abajo después de las tres rayas al response
Para poder usar este mensaje creado por nosotros primero vamos a descomentar estas dos lineas dentro del archivo package.xml
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
Ahora en nuestro archivo CmakeLists.txt
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
catkin_package(
DEPENDS message_runtime
)
generate_messages(
DEPENDENCIES
std_msgs # Or other packages containing msgs
)
Ya podemos usar mensajes creados por nosotros, ahora le diremos cual es el mensaje creado por nosotros a incluir, para ello nuevamente en el archivo CmakeLists.txt de nuestro paquete, descomentamos esta parte y añadimos el nombre del mensaje creado
add_service_files(
FILES
mensaje_servicio.srv
)
Ahora haremos catkin_make para generar los archivos de cabecera necesarios.
Si todo ha ido bien en la carpeta ~/catkin_ws/devel/include/nombre_de_nuestro_paquete/ se crearan los archivos de cabecera necesarios para poder incluirlos en nuestro código, en nuestro caso se ha creado el archivo mensaje_servicio.h
Así pues, ya tenemos todo listo para crear los dos nodos, el nodo servicio y el nodo cliente
Nodo servicio
Este es el nodo que realizará la operación, es el nodo que tomará la request y la transformará en una response, en nuestro caso tomará los valores de los enteros a sumar que le proporciona el cliente y hará la operación suma, devolviendo este valor suma mediante el response del mensaje que hemos creado.
Lo primero es crear un archivo que llamaremos servicio.cpp dentro de la carpeta src donde incluiremos el código del servicio, luego le diremos a nuestro paquete, a través del archivo CmakeLists.txt que queremos incluir dicho archivo en el paquete, para ello incluiremos las siguientes lineas en el archivo CmakeLists.txt, hay que tener en cuenta que se debe hacer en el lugar adecuado del archivo CmakeLists.txt (ver archivos en Github)
add_executable(suma_servicio src/servicio.cpp)
target_link_libraries(suma_servicio ${catkin_LIBRARIES})
una vez hecho esto ya podemos añadir el código al archivo servicio.cpp
#include <ros/ros.h>
#include "test_servicios/mensaje_servicio.h"
bool suma(test_servicios::mensaje_servicioRequest &req, test_servicios::mensaje_servicioResponse &res){
res.sum = req.x + req.y;
ROS_INFO("La suma es: %d", res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "nodo_suma_servicio");
ros::NodeHandle node;
ros::ServiceServer service = node.advertiseService("servicio_suma", suma);
ros::spin();
return 0;
}
Ahora vamos a explicar el código:#include <ros/ros.h>
#include "test_servicios/mensaje_servicio.h"
Cargamos la librería principal de ROS e incluimos la cabecera del mensaje que hemos creado.
int main(int argc, char **argv)
{
ros::init(argc, argv, "nodo_suma_servicio");
ros::NodeHandle node;
ros::ServiceServer service = node.advertiseService("servicio_suma", suma);
ros::spin();
return 0;
}
En la función main inicializamos el nodo, al que llamaremos «nodo_suma_servicio«
Creamos el servicio, al que llamamos «servicio_suma«, e indicamos que cada vez que se llame al servicio se ejecute la función suma.
bool suma(test_servicios::mensaje_servicioRequest &req, test_servicios::mensaje_servicioResponse &res){
res.sum = req.x + req.y;
ROS_INFO("La suma es: %d", res.sum);
return true;
}
Luego añadimos el spin, qué es donde se realiza verdaderamente la llamada al servicio
La función suma, que es declarada antes del main, toma como argumentos un objeto del mensaje que hemos creado, por lo tanto este objeto tendrá sus atributos, es decir una parte request que llamaremos req y que tendrá x e y como argumentos y otra parte llamada response, que tendrá sum como argumento, en esta función definimos que sum del response es la suma de x + y del request.
Nodo cliente
Ahora crearemos el nodo cliente, que será el encargado de pasar los argumentos del request al servicio, en este caso va a pasar unos valores de x e y predeterminados para que el nodo servicio los sume.
Pero primero debemos, tal y como hemos hecho con le nodo cliente, crear un archivo en el que vamos a meter el código y decirle a nuestro paquete que lo incluya, para ello en el archivo CmakeLists.txt, este archivo será cliente.cpp
add_executable(suma_cliente src/cliente.cpp)
target_link_libraries(suma_cliente ${catkin_LIBRARIES})
una vez hecho esto ya podemos añadir el código al archivo cliente.cpp
include
include «test_servicios/mensaje_servicio.h»
include <ros/ros.h>
include «test_servicios/mensaje_servicio.h»int main(int argc, char **argv)
{
ros::init(argc, argv, "nodo_suma_cliente");
ros::NodeHandle node;
ros::ServiceClient client = node.serviceClient("servicio_suma");
test_servicios::mensaje_servicio msj;
msj.request.x = 3;
msj.request.y = 4;
client.call(msj);
return 0;
}
vamos a explicarlo:
include <ros/ros.h>
include "test_servicios/mensaje_servicio.h"
Estas dos primeras lineas igual que en el nodo servicio
int main(int argc, char **argv)
{
ros::init(argc, argv, "nodo_suma_cliente");
ros::NodeHandle node;
En el main vamos a crear el nodo llamado nodo_suma_cliente
ros::ServiceClient client = node.serviceClient("servicio_suma");
Creamos un cliente de servicios en el que inidcamos que el mnsaje que le vamos a asar es del tipo «test_servicios::mensaje_servicio» y le decimos que el servicio al que se debe conectar se llama «servicio_suma«
test_servicios::mensaje_servicio msj;
msj.request.x = 3;
msj.request.y = 4;
Creamos un objeto llamado msj del tipo del mensaje_servicio que hemos creado y lo rellenamos con los valores x e y a sumar
client.call(msj);
Por último llamamos al servicio y le pasamos el objeto msj creado y rellenado anteriormente
Ahora compilamos:
$ catkin_make
Ejecutamos roscore:
$ roscore
Ejecutamos el cliente del servicio que hemos creado ahora mimso, lo haremos mediante le comando rosrun:
$ rosrun test_sevicios suma_cliente
test_servicios es el nombre del paquete y suma_cliente es el nombre que le hemos dado en CmakeLists.txt, concretamente en las líneas:
add_executable(suma_cliente src/cliente.cpp)
target_link_libraries(suma_cliente ${catkin_LIBRARIES})
Por pantalla no veremos nada, ya que no está el servidor del servicio inicializado, por lo que ejecutaremos
$ rosrun test_sevicios suma_servico
y nuevamente:
$ rosrun test_sevicios suma_cliente
por lo que en la ventana del servicio veremos:

Aquí acaba esta entrada sobre la comunicación usando servicios. En definitiva los servicos es un tipo de comunicación en la que un nodo servidor nos va a realizar algo tras recibir por parte de un nodo cliente unos argumentos.
Espero que os haya sido de utilidad, hasta la próxima entrada!!