Professional Documents
Culture Documents
INTRODUCCIN
ROS es un meta-sistema operativo para robots, es decir, una librera de cdigo que debe ser
instalada en un sistema operativo y que ser invocada desde un programa ejecutable. Entre sus ventajas
destaca que se abstrae del hardware, controla los dispositivos a bajo nivel, implementa las
funcionalidades ms comunes en el manejo de robots, intercambia mensajes entre procesos y es capaz
de administrar paquetes de cdigo. Tambin proporciona libreras y herramientas para obtener,
construir, escribir y ejecutar cdigo a travs de mltiples ordenadores.
ROS engloba una estructura o red del tipo p2p (peer to peer o puesto a puesto) donde los
procesos se comunican entre ellos usando la infraestructura de comunicaciones de ROS. Esta
comunicacin es sncrona cuando se utilizan servicios (comunicacin tipo peticin/respuesta). Es
asncrona cuando se usan topics o temas; un topic es un bus por donde circulan mensajes
intercambiados por nodos, que son pequeos programas que hacen tareas.
Para programar elementos ROS pueden usarse 3 lenguajes: c++, python y Lisp.
- Servicios: Este sistema atiende al modelo peticin/respuesta. Un nodo ofrece un servicio bajo
un nombre determinado y un nodo cliente usa el servicio enviando un mensaje de peticin y esperando
un mensaje de respuesta.
- Bolsas (bags): La bolsa es un mecanismo para guardar mensajes y reproducirlos
posteriormente. Es de una importancia vital por ejemplo para guardar datos de sensores.
El Maestro acta como un servidor de nombres. Los nodos se comunican con el Maestro para
facilitarle sus informaciones de registro. Como esos nodos se comunican con el maestro, pueden recibir
informacin de otros nodos registrados y establecer comunicacin.
El protocolo ms comn usado para comunicar nodos directamente es TCPROS, basado en los
sockets estndar TCP/IP.
Los nombres son muy importantes en ROS. Todos los elementos computacionales en ROS
tienen nombre: los nodos, los topics, los servicios y los parmetros. Todas las libreras cliente de ROS
soportan el remapeo de nombres por lnea de comandos, lo que permite que un programa ya compilado
pueda ser reconfigurado en tiempo real para operar en una topologa computacional diferente.
Por ejemplo, para controlar un lser Hokuyo, podemos iniciar el nodo-driver hokuyo, el cual
dialoga con el dispositivo lser y publica mensajes del tipo LaserScan en el topic scan. Para procesar
esos datos, debemos programar un nodo usando filtros laser que se suscriben a los mensajes del topic
scan. Tras la subscripcin, nuestro filtro podra automticamente iniciarse recibiendo mensajes desde
el lser.
Observa como los dos extremos estn desconectados. El nodo hokuyo publica datos del lser
sin saber quin est subscrito. El nodo filtro est subscrito al sensor sin saber qu nodo publica sus
datos. Los dos nodos pueden ser iniciados, finalizados y reiniciados, en cualquier orden, sin que se
produzcan errores.
Si ms tarde se aade otro lser a nuestro robot, necesitaremos reconfigurar nuestro sistema.
Todo lo que necesitamos es remapear los nombres que son usados. Cuando iniciamos nuestro primer
nodo hokuyo, podemos indicarle que remapee scan a base_scan y hacer lo mismo con el nodo filtro.
Ahora esos nodos se comunicarn usando el topic base_scan sin escuchar ya mensajes del topic scan.
Hecho esto podemos iniciar un nuevo nodo hokuyo.
En primer lugar nos aseguramos que ROS est correctamente instalado y definido de forma
correcta. Ejecutamos en un terminal la siguiente instruccin:
$ export | grep ROS
Como resultado, debe darnos una serie de definiciones como el path de ROS, el host_name, el
local host, etc
El entorno o espacio de trabajo (Workspace) est definido en la variable de entorno
ROS_PACKAGE_PAT. Podemos ver su valor:
$ echo $ROS_PACKAGE_PATH
En la distribucin OpenQbo ser: /opt/ros/electric/stacks
La estructura de las carpetas en el disco duro parte de la raz del entorno de trabajo
(/opt/ros/electric/stacks) Todas las carpetas que cuelguen de ah, son pilas o stacks. Dentro de cada pila
puede haber varios paquetes (varias subcarpetas) o un solo paquete. En el caso de que la pila contenga
un nico paquete veremos que directamente el directorio tiene carpetas como bin, config, launch o src.
Adems, contendr archivos como manifest.xml, etc. En el caso de la distribucin OpenQbo existe la
carpeta /qbo_stack que cuelga del directorio /stacks de ROS.
Existen varios comandos para moverse entre pilas y paquetes, como rospack, roscd, etc
pero es ms cmodo utilizar la ventana del S.O.
Hablemos ahora de roscore. Para poder usar ROS hay que iniciar su estructura mediante la
instruccin roscore. En la distribucin de OpenQbo, roscore se autoejecuta al iniciar el sistema. Si
intentamos ejecutar roscore, el sistema nos advertir (en rojo) que ya se encuentra en ejecucin (es el
caso de la distribucin OpenQbo).
El comando de la figura superior visualiza una lista de todos los topics actuales
Veamos ahora el comando rostopic type utilizado para ver el tipo de un topic:
Un servicio es otra forma en la que un nodo puede comunicarse con el resto. Los servicios
permiten a los nodos enviar una peticin y recibir una respuesta.
Veamos el comando rosservice:
El comando list muestra los servicios que proporciona un nodo. En el caso del nodo turtlesim
son 9:
Al ejecutar el comando type sobre el servicio clear, rosservice devuelve que el servicio est
vaco. Esto ocurre cuando la llamada a un servicio se hace sin argumentos (no enva datos al hacer una
8
peticin y no recibe datos al recibir una respuesta). Simplemente el servicio acta como un programa
que hace una determinada tarea sin necesidad de recibir argumentos y sin devolver datos.
Para ejecutar un servicio se usa el siguiente comando:
Veamos un servicio con parmetros, por ejemplo spawn. Este es un servicio utilizado por el
nodo turtlesim_node, as que vamos a ejecutarlo primero en un terminal:
$ rosrun turtlesim turtlesim_node
Ahora vamos a ver cules son sus parmetros o argumentos (teclear en otro terminal):
Obsrvese que este servicio tiene 4 parmetros de entrada y uno de salida. Al hacer:
Hemos llamado al servicio spawn indicando sus 4 argumentos. La llamada al servicio devuelve
el parmetro name (que es el nombre de una nueva tortuga):
Veamos ahora el comando rosparam:
Arriba vemos 4 parmetros con sus correspondientes valores. Los parmetros, de la misma
forma que ocurre con los nodos y topics, se ordenan por jerarquas:
9
En el ejemplo superior observamos que hay 3 parmetros (los 3 primeros). Vamos a modificar
el valor de uno de ellos:
Con esto hemos modificado el valor del parmetro. Si queremos ver el contenido del servidor
de parmetros al completo, escribimos:
10
Veremos una ventana que nos informar de la creacin de los directorios y archivos que forman
el paquete:
11
Al final recomienda echar una mirada al archivo manifest.xml que es donde se describe la
estructura del paquete.
Si nos desplazamos al directorio del paquete recin creado podemos ver que ha creado una
estructura como la de la siguiente figura:
Ahora vamos a asegurarnos que ROS puede ver el paquete. A menudo es til ejecutar rospack
profile para que se reflejen los cambios de directorios en nuestro PATH. Luego podemos buscar el
paquete:
Respecto a las dependencias, se denominan dependencias de primer orden a las que hemos
indicado en el momento de creacin del paquete:
As, si testeamos las dependencias de nuestro paquete, se vern las directas e indirectas:
12
Estos archivos de texto se utilizan para describir los elementos y parmetros de los mensajes y
servicios. Si por ejemplo programamos un nodo servicio con 2 parmetros de entrada y uno de salida,
estos parmetros hay que definirlos en un archivo .srv asociado a dicho servicio.
Los archivos msg son simples archivos de texto que describen los campos de un mensaje ROS.
Estos archivos son utilizados para generar cdigo fuente de los mensajes en diferentes lenguajes
durante el proceso de compilacin.
Los archivos srv describen un servicio. Se componen de dos partes separadas por guiones: una
peticin y una respuesta.
Los archivos msg se almacenan en el directorio /msg dentro del paquete y los archivos srv
dentro del directorio /srv
Dentro de un archivo msg se pueden utilizar los tipos int8, int16, int32, int64, uint8, uint16,
Tambin los float32, float64, string, time, duration, array[] as como otros archivos msg.
Se comienza por el tipo especial de ROS llamado Header.
Un ejemplo de archivo msg:
Los archivos srv son similares a los msg, excepto que tienen dos partes: una peticin al principio
y una respuesta tras los tres guiones. Veamos un ejemplo:
13
Para crear un mensaje se crea un fichero .msg en la carpeta /msg del paquete con el contenido
deseado. Para nuestro ejemplo creamos el archivo Num.msg con el siguiente contenido:
int64 num
Podemos crear el archivo utilizando nuestro editor favorito o bien desde un terminal de la
siguiente forma:
Para asegurarnos que se generar el mensaje cuando compilemos el paquete, hay que abrir el
archivo CMakeLists.txt en un editor y quitar el smbolo de comentario (#) de la siguiente lnea:
# rosbuild_genmsg()
Creado el mensaje, podemos comprobar que ROS lo ve utilizando el comando rosmsg show
Ejemplo:
Pasemos ahora a los servicios. Para crear un servicio generamos previamente un archivo dentro
de la carpeta /srv con el contenido de los parmetros a utilizar por dicho servicio. Para nuestro ejemplo,
creamos un archivo llamado AddTwoInts.srv con el siguiente contenido:
int64 a
int64 b
--int64 sum
Finalmente tenemos que quitar del archivo CMakeLists.txt el # de la lnea:
# rosbuild_gensrv()
Al igual que antes podemos asegurarnos que ROS lo ve.
Finalmente, al haber creado un archivo .msg y/o un archivo .srv, hay que volver a compilar el
paquete:
$ rosmake beginner_tutorials
14
Nodo es el trmino ROS para un ejecutable que conecta a la red ROS. Vamos a crear un nodo
publicador que emitir continuamente un mensaje a dicha red.
Lo primero que debemos hacer es desplazarnos con roscd al directorio de nuestro paquete:
Ahora, dentro de la carpeta /src, vamos a crear un archivo (para este ejemplo, talker.cpp) con el
siguiente contenido:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/**
* Este tutorial muestra cmo enviar mensajes al sistema ROS
*/
int main(int argc, char **argv)
{
/**
* La funcin ros::init() inicializa el nodo talker en el sistema ROS. Le pasamos los argumentos para la lnea de comandos
* y un tercer argumento con el nombre del nodo.
*/
ros::init(argc, argv, "talker");
/**
* NodeHandle es el principal punto de acceso para comunicarse con el sistema ROS
*/
ros::NodeHandle n;
/**
* La funcin advertise() sirve para decirle a ROS que queremos publicar en un topic determinado.
* (en este caso el topic se llama chatter y estamos indicando que el mensaje ser de tipo String)
* Con esto hacemos una llamada al nodo maestro de ROS, el cual lleva un registro de qu nodos estn
* publicando y qu nodos estn subscritos. advertise() devuelve un objeto publicador que te permite
* publicar mensajes en el topic mediante una llamada a la funcin publish().
*
* El segundo parmetro de advertise() es el tamao de la cola del mensaje usada para publicar mensajes.
* Si los mensajes son publicados ms deprisa de lo que puedan enviarse, se guardarn en la cola.
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10); //Define la frecuencia en hercios de polling (10 veces por segundo).
/**
* Contador con el nmero de mensajes enviados. Se usa para crear un nic string por cada mensaje.
*/
int count = 0;
while (ros::ok()) //Mientras el sistema ROS est operativo
{
/**
* Objeto mensaje donde cargamos los datos para despus publicarlo.
*/
15
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str(); //data es un miembro del tipo String. String es un archivo .msg
ROS_INFO("%s", msg.data.c_str()); //este es el equivalente ROS a printf y cout
/**
* Con la funcin publish() enviamos los mensajes. El parmetro es el objeto mensaje
* El tipo del objeto debe coincidir con el que pusimos en advertise()
*/
chatter_pub.publish(msg);
/**
* Aqu no es necesaria, pero spinOnce() se pone para atender a las funciones callback.
* Si hubisemos aadido una subscripcin en esta aplicacin, no funcionara sin esta llamada.
*/
ros::spinOnce();
loop_rate.sleep(); //Aplica el sleep que definimos antes. Publica 10 veces/seg
++count;
}
return 0;
}
NOTA FINAL: Para crear este nodo en el paquete, debemos editar el archivo CMakeLists.txt del
directorio y aadir al final la siguiente lnea:
Rosbuild_add_executable(talker src/talker.cpp)
Esto crear un ejecutable talker, que por defecto se ubicar en la carpeta /bin.
Ahora, ejecutar:
$ make
Al igual que antes, nos desplazamos al directorio de nuestro paquete usando roscd y creamos el
archivo listener.cpp dentro de la carpeta /src.
#include "ros/ros.h"
#include "std_msgs/String.h"
/**
* Este tutorial muestra un simple nodo receptor de mensajes.
*/
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
/**
* init() inicializa el nodo listener en el sistema ROS
*/
ros::init(argc, argv, "listener");
16
/**
* n es el manejador del nodo.
*/
ros::NodeHandle n;
/**
* Mediante la funcin subscribe() indicamos a ROS que queremos recibir los mensajes del topic chatter
* Los mensajes se pasan a una funcin de callback (aqu llamada chatterCallback)
* El nmero 1000 indica el tamao de la cola de mensajes recibidos.
*/
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* ros::spin() hace que se entre en un polling buscando mensajes. Cuando llega un mensaje al topic, se llama a la funcin
* chatterCallback() indicada antes.
* Hay otras formas de hacer esto.
*/
ros::spin();
return 0;
}
NOTA FINAL: Para crear este nodo en el paquete, debemos editar el archivo CMakeLists.txt del
directorio y aadir al final la siguiente lnea:
rosbuild_add_executable(listener src/listener.cpp)
Esto crear un ejecutable listener, que por defecto se ubicar en la carpeta /bin.
Ahora, ejecutar:
$ make
A diferencia de C++, el cdigo fuente escrito en python se denomina script. Hay que guardarlo
en la carpeta /scripts del paquete.
Creamos el archivo talker.py con el siguiente contenido:
#!/usr/bin/env python
#Esta linea asegura que el script ser ejecutable
import roslib; roslib.load_manifest('beginner_tutorials')
#importa las libreras
import rospy #imprescindible para un nodo python
from std_msgs.msg import String
#importa el tipo String
def talker():
#describe el interfaz de talker
pub = rospy.Publisher('chatter', String) #declara que el nodo publicar un String en el topic chatter
rospy.init_node('talker') #indica el nombre del nodo
while not rospy.is_shutdown():
#mientras el sistema est en funcionamiento
str = "hello world %s" % rospy.get_time()
#construye el mensaje
rospy.loginfo(str)
pub.publish(String(str))
#publica el mensaje
rospy.sleep(1.0)
#Hace una pausa
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
17
pass
def callback(data):
rospy.loginfo(rospy.get_name() + ": I heard %s" % data.data)
def listener():
rospy.init_node('listener', anonymous=True)
#declara el nodo. Con anonymous podemos tener varios listener
rospy.Subscriber("chatter", String, callback)
#se suscribe al topic chatter para recibir un string
rospy.spin() #hace que el nodo permanezca a la escucha infinitamente (no funciona como en c++)
if __name__ == '__main__':
listener()
Ejecutamos el publicador:
18
El nodo que vamos a programar recibe 2 nmeros enteros y devuelve la suma de ellos. Para
que funcione este ejemplo, debemos tener en la carpeta /srv un fichero llamado AddTwoInst.srv con
el contenido siguiente (y haberlo compilado en el paquete mediante rosmake):
NOTA: Al compilar el archivo srv, se genera automticamente un archivo include (.h) asociado.
Nos desplazamos al directorio de nuestro paquete con roscd y creamos add_two_ints_server.cpp en la
carpeta /src con el siguiente contenido:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
// Esta funciona recoge los argumentos de la peticin (req) y de la respuesta (res) para trabajar con ellos
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server"); //Inicializa el nodo servicio
ros::NodeHandle n; //manejador del nodo
19
El nodo servicio est a disposicin de los nodos denominados cliente. Para programar un
nodo cliente, creamos en /src el archivo add_two_ints_client.cpp:
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
// Lo siguiente crea un cliente add_two_ints para el servicio AddTwoInts
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
beginner_tutorials::AddTwoInts srv; //Instanciamos la estructura del archivo srv
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if (client.call(srv)) //llamada al nodo servicio con los argumentos definidos justo arriba
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
Para construir estos nodos, editamos el archivo CMakeLists.txt de nuestro paquete y aadimos
las siguientes lneas al final:
De forma similar a c++ y teniendo creado y empaquetado el archivo .srv, creamos el archivo
add_two_ints_server.py en la carpeta /scripts del paquete.
20
#!/usr/bin/env python
import roslib; roslib.load_manifest('beginner_tutorials')
from beginner_tutorials.srv import *
import rospy
def handle_add_two_ints(req):
print "Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b))
return AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node('add_two_ints_server')
s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
print "Ready to add two ints."
rospy.spin()
if __name__ == "__main__":
add_two_ints_server()
Ahora ejecutamos el nodo cliente en otro terminal con los argumentos en la lnea de
comandos:
USANDO ROSLAUNCH
Este tutorial explica el uso de roslaunch para iniciar varios nodos al mismo tiempo.
22
</launch>
La etiqueta <launch> identifica el archivo xml como de tipo launch. Vemos tambin que se
inician dos grupos con espacio de nombres denominado turtlesim1 y turtlesim2. En realidad se
estn lanzando 2 nodos turtlesim, cada uno llamado de forma distinta. Con estas definiciones
iniciaremos dos simuladores sin conflictos de nombres. El ltimo bloque del archivo inicia el nodo
mimic con los topics de entrada y salida renombrados a turtlesim1 y turtlesim2
Ahora lanzamos el archivo mediante roslaunch:
Veremos que se abren dos ventanas con las tortugas. En una nueva ventana en viamos el
comando rostopic siguiente:
23