topics

ROS2. Publicadores y Subscriptores

1 Introducción

En ROS los nodos se comunican entre sí empleando topics. El concepto de topic podría asemejarse a un bus de datos al que se le asigna un nombre, pudiendo algunos nodos (denominados publicadores) conectarse al mismo para escribir mensajes  y otros  (denominados subscriptores) para recibirlos.

En este artículo vamos a crear un nuevo paquete que llamaremos py_pubsub al cual incorporaremos un nodo publicador que va a escribir mensajes en un topic al que simplemente hemos asignado el nombre de “topic”, y otro nodo subscriptor que los leerá y mostrará la información en el terminal. 

2 Creando un paquete python

Para crear el paquete vamos a la carpeta src de nuestro espacio de trabajo y ejecutamos el siguiente comando:

ros2 pkg create --build-type ament_python py_pubsub

3 Añadiendo un nodo Publicador

El siguiente paso es crear nuestro nodo publicador.

Para ello, en nuestro paquete dentro de la carpeta que tiene su mismo nombre, creamos un archivo al que vamos a llamar publisher.py:

cd py_pubsub/py_pubsub
touch publisher.py

Abrimos el archivo y pegamos una de las dos versiones de código que se muestran a continuación. La diferencia entre ellas es que en una se ha utilizado programación orientada a objetos y en la otra no.

Básicamente ambos realizan las siguientes acciones:

    1. Crea un nuevo nodo a partir de la clase rclpy.node.
    2. Crea un objeto publisher mediante la función create_publisher( ) de la clase rclpy.node, a la que se le pasa como parámetros la estructura de datos que usarán los mensajes, el nombre del topic al que se quiere escribir y el tamaño de la cola a emplear en el caso de que no se puedan procesar los mensajes lo suficientemente rápido.
    3. Crea un temporizador mediante la función create_timer( ) de la clase rclpy.node a la que se le pasa el nombre de una función que se ejecutará periódicamente.
    4. Crea la función que se va a ejecutar periódicamente y en la cual se ejecuta la función publish( ) del objeto publisher para enviar el mensaje al topic.
    5. Llama a la función rclpy.spin( ) que mantiene ejecutándose el bucle interno del nodo.

3.1 Usando una clase

import rclpy
from rclpy.node import Node

from std_msgs.msg import String


class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello World: %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i += 1


def main(args=None):
    rclpy.init(args=args)

    minimal_publisher = MinimalPublisher()

    rclpy.spin(minimal_publisher)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Código obtenido de: https://github.com/ros2/examples/

 

3.2 Usando una función local


import rclpy

from std_msgs.msg import String


def main(args=None):
    rclpy.init(args=args)

    node = rclpy.create_node('minimal_publisher')
    publisher = node.create_publisher(String, 'topic', 10)

    msg = String()
    i = 0

    def timer_callback():
        nonlocal i
        msg.data = 'Hello World: %d' % i
        i += 1
        node.get_logger().info('Publishing: "%s"' % msg.data)
        publisher.publish(msg)

    timer_period = 0.5  # seconds
    timer = node.create_timer(timer_period, timer_callback)

    rclpy.spin(node)

    # Destroy the timer attached to the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    node.destroy_timer(timer)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Código obtenido de: https://github.com/ros2/examples/

 

4 Añadiendo el nodo Subscriptor

Creamos mediante el siguiente comando, en la misma ubicación,  el archivo subscriber.py que va a contener el código de nuestro nodo subscriptor. 

touch subscriber.py

Al igual que para el caso del publicador se muestran a continuación dos versiones de código por lo que solo tenemos que abrir el archivo que acabamos de crear y copiar en él cualquiera de ellas.

Las principales acciones que realiza el código son:

    1. Crea un nuevo nodo a partir de la clase rclpy.node.
    2. Crea un objeto subscription mediante la función create_subscription( ) de la clase rclpy.node, a la que se le pasa como parámetros la estructura de datos que usarán los mensajes, el nombre del topic que queremos leer, el tamaño de la cola que empleará el subscriptor en el caso de que no sea capaz de procesar los mensajes lo suficientemente rápido y la función de callback que se llamará cada vez que llegue un mensaje.
    3. Define la función de callback, la cual lee el mensaje recibido y muestra la información en el terminal.
    4. Llama a la función rclpy.spin( ) que mantiene ejecutándose el bucle interno del nodo.

4.1 Usando una clase

import rclpy
from rclpy.node import Node

from std_msgs.msg import String


class MinimalSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning

    def listener_callback(self, msg):
        self.get_logger().info('I heard: "%s"' % msg.data)


def main(args=None):
    rclpy.init(args=args)

    minimal_subscriber = MinimalSubscriber()

    rclpy.spin(minimal_subscriber)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Código obtenido de: https://github.com/ros2/examples/

 

4.2 Usando una función local


import rclpy

from std_msgs.msg import String


def main(args=None):
    rclpy.init(args=args)

    node = rclpy.create_node('minimal_subscriber')

    subscription = node.create_subscription(
        String, 'topic', lambda msg: node.get_logger().info('I heard: "%s"' % msg.data), 10)
    subscription  # prevent unused variable warning

    rclpy.spin(node)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Código obtenido de: https://github.com/ros2/examples/

 

5 Modificando el archivo package.xml para añadir las dependencias

Abrimos el archivo package.xml del paquete para añadir las dependencias empleadas en el código del publicador y del subscriptor, que son  rclpy y std_msgs

El contenido del archivo nos debe quedar como se muestra a continuación, en el cual se ha utilizado la etiqueta <exec_depend> para indicar las dependencias:


<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>py_pubsub</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="TODO@gmail.com">ros</maintainer>
  <license>TODO: License declaration</license>

  <depend>rclpy,</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

6 Incorporando los puntos de entrada en setup.py

El último paso es editar el archivo setup.py para añadir al array entry_points los puntos de entrada para de los scripts del publicador y subscriptor, de modo que puedan ejecutarse mediante ros2 run

from setuptools import setup

package_name = 'py_pubsub'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='ros',
    maintainer_email='TODO@gmail.com',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
              'publisher = py_pubsub.publisher:main',
              'subscriber = py_pubsub.subscriber:main',
        ],
    },
)

7 Ejecutando los nodos

Una vez que ya tenemos preparado nuestro paquete lo compilamos desde el directorio raíz de nuestro espacio de trabajo:

colcon build --packages-select py_pubsub
. install/setup.bash

Seguidamente ejecutamos el subscriptor mediante el siguiente comando:

ros2 run py_subpub subscriber

Y finalmente abrimos otro terminal y ejecutamos el publicador:

ros2 run py_subpub publisher

Si hemos seguido los pasos correctamente deberíamos ver en un terminal los mensajes enviados por el publicador y en el otro cómo los recibe el subscriptor.

publisher output
subscriber output
Comparte este artículo:

Deja un comentario

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