ROS2 con Python

por Juan Felipe Martinez Bedoya

En este tutorial se hablará acerca de utilizar ROS2 con programas de python, y la estructura que se debe manejar.

Paquetes

Estructura

Un paquete de ROS2 utilizando Python, debe tener la siguiente estructura de paquetes o archivos:

  • package.xml - contiene la meta información del paquete, como por ejemplo las dependencias.
  • setup.py - contiene las instrucciones de como compilar el paquete.
  • setup.cfg - define donde se instalaran los archivos.
  • /<nombrePaquete> - directorio que se llama igual que el paquete que contiene todos los archivos .py.
  • /launch - carpeta que contiene los archivos launch.
  • Existe otros tipios de archivos o carpetas, sin embargo, estas son las más comunes.

Creación

Para la creación de paquetes se debe instanciar el ambiente con:

source /opt/ros/foxy/setup.bash

Luego se crea un carpeta que se denomina como workspace, esta se le puede dar un nombre cualquiera, y en ella estarán todos los paquetes del proyecto.

mkdir ros2_ws
cd ros2_ws
pwd

En el workspace se debe tener una carpeta llamada src en la cual se tienen los paqeutes. Parandose en esa carpeta se usa el siguiente comando:

ros2 pkg create --build-type ament_python <nombrePaquete> --dependencies <dependenciaPaquetes>

Donde el nombrePaquete es el nombre que se le quiere dar al paquete, dependenciasPaquete es si el paquete va a depender de otro (normalmente se pone rclpy cuando se trabaja con python) y lo que va después de build-type es el tipo de paquete que se va crear, en el caso de un paquete de python se usa ament_python.

Hay una lista grande de dependencias que se puede agregar, las cuales incluye: - rclpy - sirve para poder trabajar con python.

Para poder incluir más dependencias se separan con espacios.

Luego de crear el paquete, este se debe compilar para verificar que las dependencias quedaron bien hechas y hacer un debug si es necesario. Para esto se debe estar parado en la carpeta del workspace (en este ejemplo ros2_ws) y se hace con el comando:

colcon build

Hay otra opción para compilar un paquete en específico, para esto se usa el comando:

colcon build --packages-select <nombrePaquete>

Luego de compilar el paquete es recomendable hacerle source a la carpeta de install del workspace:

source install/setup.bash

Para confirmar que el paquete quedó correctamente se puede utilizar el comando de ros2 pkg list y buscar el paquete con el nombre que se le haya asignado, si se desea buscar directamente se podría utilizar el comando de ros2 pkg list | grep <nombrePaquete>.

setup.py

En este archivo están todas las instrucciones para compilar correctamente un paquete.

Los elementos que hay que modificar para que este funcione correctamente son:

El diccionario de entry_points, este se debe modificar poniendo los ejecutables que se van a generar, con la información del nodo que se va a generar. Ejemplo:

entry_points={
            'console_scripts': [
                'simple_node = my_package.simple:main'
            ],
        }

En este se debe seguir siempre el mismo formato dentro del console_scripts, que es el siguiente: <nombreEjecutable> = <nombrePaquete>.<nombrePrograma>:main donde el nombre del ejecutable es aquel que se le dara con el cual se reconocerá el nodo, el nombre del paquete es el que es esté y el nombre del programa es el archivo que se esté llamando (en el ejemplo anterior el programa se llamaría simple.py).

El otro parámetro que toca modificar es el data_files que sirve para que colcon pueda encontrar los launch files que se vayan a generar. Para esto se pone la siguiente linea de código:

data_files=[
            ('share/ament_index/resource_index/packages',
                ['resource/' + package_name]),
            ('share/' + package_name, ['package.xml']),
            (os.path.join('share', package_name), glob('launch/*.launch.py'))
        ]

Con estas líneas se instalarán los archivos launch en la ruta de ~/ros2_ws/install/my_package/share/my_package/.

Archivos launch

Los archivos launch sirven para correr un programa que contiene dependencias en sí, para no tener que correr nodo por nodo.

Para crear un archivo launch en python se usa:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        launch_ros.actions.Node(
            package='turtlebot3_teleop',
            executable='teleop_keyboard',
            output='screen'),
    ])

Las primeras dos líneas son las librerias que hay que importar, después es una función que devolverá un objeto tipo LaunchDescription, el cual genera un nodo con los parametros, donde package es el nombre del paquete donde esta el ejecutable (executable) que se va a correr y el output el canal por donde se imprimirá los output del programa ejecutable.

Programas

Para crear programas que utilicen ROS2 en python se puede seguir el siguiente ejemplo:

#Importar las librerias necesarias
import rclpy
from rclpy.node import Node

#Definir la función principal
def main(args=None):
    # Inicia la comunicación con ROS2
    rclpy.init(args=args)
    # Imprime un mensaje en la terminal
    print("Hola Mundo!")
    # Apaga la comunicación con ROS2
    rclpy.shutdown()


if __name__ == '__main__':
    # Llama la función
    main()

nodos

Para crear un nodo sencillo en python se puede seguir el siguiente formato:

# Se importan las librerias necesarias
import rclpy
from rclpy.node import Node

# Se crea una clase MyNode que hereda de Node
class MyNode(Node):
    def __init__(self):
        # Se llama el método super() para inicializar el nodo
        # El parámetro que se pasa es elnombre del Nodo
        super().__init__('Mi nodo')
        # Se crea un timer con dos parámetros, el primero que es cada cuanto se repite y el segundo que es cual función llama cada unidad de tiempo especificada
        self.create_timer(0.2, self.timer_callback)

    def timer_callback(self):
        # Se imprime un log en el terminal
        self.get_logger().info("Hola mundo desde un nodo!")

def main(args=None):
    # Inicia la comunicación con ROS
    rclpy.init(args=args)
    # Se declara el nodo
    node = MyNode()
    # Mantiene el nodo vivo hasta que reciba una interrupción del sistem (ctrl+c)
    rclpy.spin(node)
    # Apaga la comunicación con ROS
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Enviar mensajes

Un ejemplo para crear u