Saltar al contenido
ros tutorial

Publish/Subscribe Rqt



groovy

Qué es el método de intercambio de información de ROS llamado Publish/Subscribe?? Como hacer un Publisher, y un Subscriber?? Haremos un ejemplo práctico para que estos conceptos queden claros.

En esta entrada también veremos brevemente la herramienta Rqt

ruleta 5

Qué es el método Publish/Subscribe ??

Ya hemos visto en entradas anteriores qué es un topic, ahora vamos a ver una de las maneras de intercambiar la información de estos topics.
Una de las maneras más importantes y más comunes de hacerlo es mediante el método Publish/Subcribe. Para ello lo vamos a explicar con una analogía. La analogía de la radio. Si queréis más información en el Wiki oficial podéis hacerlo en el siguiente enlace

Imaginemos que tenemos una antena transmisora de radio, de las grandes que hay en los montes, imaginemos que esta antena de radio emite música en la emisora 94.2. Ahora con una radio de mano, de las que ya casi no quedan, vamos a sintonizar la emisora 94.2 para conseguir oír la radio que queremos.

La antena de radio es la que publica el topic (es el Publisher), la radio portátil se subscribes al topic (es el Subscriber) y el topic es la onda a la que sintonizamos la radio, es decir el topic es 94.2. La música que se reproduce en nuestra radio a través de la emisora 94.2 es el mensaje del topic.

Si deseamos conectar otros dispositivos, como la radio del coche, no tenemos más que sintonizar la radio a esa emisora, es decir no hay más que subscribirse a un topic. Puede haber tantos Subscribers como queramos, y a su vez puede haber tantos Publishers como queramos, en nuestro ejemplo puede haber otra antena de radio que publique en la 94.2.

pub_sub2

Algo muy importante es que todos los publishers y subscribers están intercambiando el mismo tipo de mensaje. En nuestro ejemplo, tanto las antenas publican y las radios de mano y de coche se subscriben a mensajes digamos del tipo ”ondas de sonido”, si la radio de mano esperase subscribirse a un topic cuyo mensaje fuese del tipo “imágenes”, esta radio obviamente no recibiría nada.

Para un Publisher le es indiferente cuantos Subscribers tiene, él publica el topic y ya habrá alguien (o no) que se subscriba. Haya o no Subscribers, el Publisher seguirá mandando sus datos independientemente.

Dichos Publishers y Subscribers están contenidos en nodos, pues bien un nodo puede tener varios Publishers que publiquen topics con mensajes de diferente tipo. Un nodo puede tener un Publisher que publique imágenes y otro Publisher que publique el valor de un sensor.

Ejemplo práctico

Publisher

Ahora que ya tenemos los conocimientos más básicos de ROS, vamos a realizar un ejemplo práctico y desde cero, en el que vamos a crear un workspace (área de trabajo), dentro de esta definiremos un paquete que contendrá dos nodos, uno que publicará un topic cuyo mensaje será cadena de texto, y otro nodo que se subscribirá a ese topic.

Lo primero vamos a crear un “workspace” en el que trabajar, para ello escribiremos en la consola:

$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/
$ catkin_make

ahora hacemos source:

$source ~/catkin_ws/devel/setup.bash

Nuestro «workspace» tendrá la siguiente estructura:

workspace0

Ahora vamos a crear un paquete llamado “test”:

$ cd src
$ catkin_create_pkg test roscpp std_msgs

Este comando va a crear un paquete llamado test con dependencias “roscpp”, es decir vamos a incluir la libreria de C++ para ROS y “std_msgs” que incluye la descripción del tipo de mensaje que vamos a utilizar. Este último lo veremos luego.

Este paquete tendrá la siguiente estructura:

pkg

Si ahora echamos un ojo al archivo CmakeLists.txt de nuestro paquete(recuerda que lo tienes en nuestro Github), veremos como las dependencias que hemos escrito en la consola con el comando de crear el paquete, se han añadido al archivo.
Al ppo de este archivo a partir de la linea 10 veremos:

find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)

Si ahora vemos el archivo package.xml, al final, a partir de la linea 52 se añaden dichas dependencias automáticamente:

<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend> <build_export_depend>roscpp</build_export_depend> <build_export_depend>std_msgs</build_export_depend> <exec_depend>roscpp</exec_depend>
<exec_depend>std_msgs</exec_depend>

Si no hubiésemos puesto los comandos de las dependencias rosccpp y std_msgs al crear el paquete hubiésemos necesitado añadir estas lineas en estos archivos manualmente.

En futuras entradas hablaremos exclusivamente de los archivos CmakeLists.txt y package.xml.

Pues bien, metámonos en harina, dentro de la carpeta src de nuestro paquete vamos a meter el código de programación de nuestros nodos. Para ello vamos a la carpeta src

$ cd ~/catkin_ws/src/test/src

Y una vez allí creamos un archivo que llamaremos “publisher.cpp”. En la consola escribimos:

$ touch publisher.cpp

Así hemos creado el archivo, para editarlo escribimos en la consola (usaremos vim, aunque yo recomiendo visual studio code):

$ vim publisher.cpp

El nodo publicador tendrá el siguiente código, recuerda que tienes el código completo en Github

#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc, char **argv) {

ros::init(argc, argv, "nodo_publisher");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("mi_topic",10); ros::Rate rate(1);
while(ros::ok()){
std_msgs::String msg;
msg.data = "Mensaje publicado";
pub.publish(msg);
rate.sleep(); } }

Ahora vamos a explicar el código

#include <ros/ros.h>
#include <std_msgs/String.h>

En estas  primeras líneas introducimos los «headers»  necesarios para usar todos los elementos de la librería roscpp, así como el que incluye el mensaje del tipo String contenido en el paquete que viene con ROS llamado std_msgs. Así con estas dos lineas ya podemos usar las funciones y objectos de la librería de ros para C++ y podemos usar los mensajes del tipo String

int main(int argc, char **argv) {

Aquí definimos la función principal (main) de nuestro programa, esto siempre es así

ros::init(argc, argv, "nodo_publisher");
ros::NodeHandle nh;

En la primera linea definimos un nodo llamado «nodo_publisher», y en la segunda iniciamos el nodo, sin esta línea no habría nodo.

ros::Publisher pub = nh.advertise("mi_topic",10);

Aquí es donde verdaderamente vamos a crear el publisher, creamos un publisher llamado pub que va a publicar un topic llamado «mi_topic» cuyo mensaje es del tipo std_msgs/String. El 10 representa el numero de mensajes que puede haber en la cola de salida, no nos interesa esto ahora

ros::Rate rate(1);

Definimos una variable llamada rate que va a representar una frecuencia de 1Hz.

while(ros::ok()){
std_msgs::String msg;
msg.data = "Mensaje publicado";
pub.publish(msg);
rate.sleep(); } }

Esto lo vamos a ver mucho en ROS, vamos a crear un bucle while que realizará la tarea que hay entre corchetes, siempre y cuando se cumpla la condición entre paréntesis, es decir mientras ros::ok() , es decir mientras ros está encendido, ejecutará lo que hay entre parentesis.

Dentro del paréntesis crearemos una variable del tipo std_msgs::String llamada msg. Al ser de ese tipo, msg tendrá la siguiente estructura

string

Si nos fijamos en la parte que dice Compact Message Definition, vemos como nos dice que ese mensaje va a tener un objeto del tipo string llamado data.

Pues bien, en la siguiente linea asignamos a la variable msg, en concreto a su elemento data el valor de una cadena de caracteres (string) cuyo contenido es «Mensaje publicado»
Es decir, rellenamos el objeto msg.data con un string, ya que esa variable creada por nosotros al ser de ese tipo sólo admite strings.

Ahora le decimos al publisher creado por nosotros llamado pub, que publique el msg.

Y por último llamamos a la función sleep
de la variable rate (definida anteriormente) y que lo que hace es esperar hasta que se cumpla el tiempo necesario para publicar el mensaje con frecuencia de 1Hz

Antes de probar este nodo, hay que decir en el archivo CmakeLists.txt del paquete, que nuestro código está en el archivo publisher.cpp y que ejecute este código, para ello en el archivo CmakeLists.txt añadimos las siguientes lineas a partir de la línea 136

add_executable(publisher src/publisher.cpp)
target_link_libraries(publisher ${catkin_LIBRARIES})

Lo que hacemos es decirle al compilador de Cmake que el archivo ejecutable se llama publisher y el archivo a compilar, donde está el código es el archivo publisher.cpp

Ahora vamos a la raiz de nuestro workspace

$ cd ~/catkin_ws

y ejecutamos el comando catkin_make

$ catkin_make

Si todo a ido correcto no nos dará ningún error y podremos ejecutar nuestro nodo, recuerda que debes tener roscore ejecutado, si no es así escribe :

$ roscore

ahora ejecutamos el nodo:

$ rosrun test publisher

ejecutamos un archivo ejecutable dentro del paquete “test” llamado “publisher”, como hemos visto antes, así lo hemos decidido en la línea del Cmakeslists.txt:

add_executable(publisher src/publisher.cpp)

si todo ha ido bien no veremos nada en pantalla, pero si introducimos el comando:

$ rosnode list


rosnode

veremos los nodos disponibles, entre ellos aparece “nodo_publisher” que es el nodo que hemos creado en nuestra línea del código:

ros::init(argc, argv, "nodo_publisher");

Si ahora escribimos en la consola:

$ rostopic list

nos saldrá la lista de topics activos y entre ellos “/mi_topic”, que es el topic publicado seguń la linea:

ros::Publisher pub = nh.advertise("mi_topic",10);

Vamos a ver más info de este topic, para ello usamos el comando

$ rostopic info /mi_topic

Nos dice que tipo de mensaje lleva este topic, en este caso es un String correspondiente a los mensajes del tipo std_msgs

rostopic_info

Para ver el valor actual de este topic escribimos:

$ rostopic echo /mi_topic


rostopic_echo

vemos como se publica el mensaje con frecuencia de 1 Hz.

Subscriber

Ahora vamos a ver el nodo subscriptor , igual que antes creamos el archivo subscriber.cpp dentro dela carpeta src del paquete test

$ cd ~/catkin_ws/src/test/src
$ touch subscriber.cpp

y lo editamos:

$ vim subscriber.cpp

El nodo subscriptor tendrá el siguiente código, recuerda que tienes el código completo en GitHub

#include <ros/ros.h>
#include <std_msgs/String.h>
void callback_subscriber(const std_msgs::String& msg){
ROS_INFO("Subscrito al mensaje: %s", msg.data.c_str());
}
int main(int argc,char **argv){
ros::init(argc,argv,"nodo_subscriber");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("mi_topic",1000,callback_subscriber);
ros::spin();
}

vamos a explicarlo:

#include <ros/ros.h>
#include <std_msgs/String.h>

Estas dos primeras lineas igual que en el nodo publicador


int main(int argc,char **argv){
ros::init(argc,argv,"nodo_subscriber");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("mi_topic",1000,callback_subscriber);
ros::spin();
}

Ahora vamos a centrarnos en la función principal, primero al igual que en el publisher creamos un nodo al que hemos llamado nodo_subscriber y lo iniciamos.

Ahora creamos un subscriptor llamado sub, que se subscribe al topic llamado mi_topic  (topic que hemos creado en el nodo publicador), y que cada vez que detecta que hay una novedad en el topic al que se subscribe, va a ejecutar una función que hemos llamado callback_subscriber y que hemos definido en nuestro código antes de la función principal.

Dicha función tiene el siguiente código: 

void callback_subscriber(const std_msgs::String& msg){
ROS_INFO("Subscrito al mensaje: %s", msg.data.c_str());

Lo que hace esta función es, mandar a la consola un texto que pone «Subscrito al mensaje: » y pone el mensaje recibido en el topic. A esta función le pasamos como argumento un objeto msg del tipo String, que es del tipo al que pertenece el mensaje del topic al que  nos subscribimos. Por tratarse de un tipo String de los std_msgs, tendrá una variable data que es lo que publica el nodo publisher.

string

Ahora modificamos el archivo CmakelLiists.tct para poder ejecutar nuestro archivo, añadiendo en el CmakeLists.txt del paquete:

add_executable(subscriber src/subscriber.cpp)
target_link_libraries(subscriber ${catkin_LIBRARIES})

Lo que hacemos es decirle al compilador de Cmake que el archivo ejecutable se llama subscriber y el archivo a compilar, donde está el código es el archivo subscriber.cpp

Ahora ejecutamos roscore:

$ roscore

Ejecutamos el publisher:

$ rosrun test publisher

Y ahora el recién creado  publisher:

$ rosrun test subscriber

Por pantalla veremos:

rosrunsub

Ahora escribimos en la consola :

$ rosnode list

Este comando nos devuelve los nodos activos, y vemos claramente como están activos tanto el nodo publisher y el nodo subscriber  

RQT

Vamos a introducir una herramienta que se llama RQT que no es más que una herramienta para ver los parámetros de nuestro robot de una manera gráfica, si bien no la vamos a ver en profundidad sí que vamos a ver la herramienta rqt_graph, muy útil para ver qué nodos publican y se subscriben a qué topics.

Para iniciar esta herramienta deberemos escribir en la consola:

$ rqt_graph

Esto nos va a devolver por pantalla la siguiente imagen:

esta imagen reprsenta los nodos activos, en este caso el publisher y el subscriber, y unidos por una flecha, el topìc que usan para comunicarse.

Esta herramienta simplifica mucho la visión de nuestro proyecto sobre todo cuanto más complejo es este. Es una de mis herramientas preferidas, pues hay que tener en cuenta que es una herramienta muy sencilla de utilizar y que nos aporta un gran beneficio de manera rápida y nos ayuda a tener una visión del esquema de nuestro proyecto.

Aquí acaba esta entrada sobre la comunicación pub/sub, se pide esfuerzo al alumno de comprender bien este tipo de comunicación, ya que es la más usada. A grosso modo se puede decir que una aplicación ROS consiste en crear nodos que publican o se subscriben a topics.

Por ejemplo pensemos en un robot que es un manipulador industrial, y que según vea un objeto por una cámara lo cogerá o no.
Dicho robot tendrá un paquete para la cámara que recogerá las imágenes y las publicará en un topic, por ejemplo /imagenes. Otro paquete se subscribirá a dicho topic /imagenes y decidirá la posición de la caja en el espacio. En caso de ser alcanzable por el robot, publicará un topic por ejemplo /posiciones con las posiciones necesarias de cada eje para alcanzar la posición del objeto, permitiéndose así el movimiento del robot.

En la siguiente entrada hablaremos sobre dos conceptos muy importantes, Rosparam y  Roslaunch.

Aviso Legal

Política de cookies

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

5 Publish/Subscribe Rqt

  • por