Écrire un simple éditeur et abonné (Python)

Objectif : Créer et exécuter un nœud d’éditeur et d’abonné à l’aide de Python

Niveau du tutoriel : Débutant

Durée : 20 minutes

Arrière-plan

Dans ce didacticiel, vous allez créer des nodes qui se transmettent des informations sous forme de messages de chaîne via un : doc:sujet. L’exemple utilisé ici est un simple système « locuteur » et « auditeur » ; un nœud publie des données et l’autre s’abonne au sujet afin de pouvoir recevoir ces données.

Le code utilisé dans ces exemples peut être trouvé ici.

Conditions préalables

Dans les tutoriels précédents, vous avez appris à créer un espace de travail et créer un package.

Une compréhension de base de Python est recommandée, mais pas entièrement nécessaire.

Tâches

1 Créer un package

Ouvrez un nouveau terminal et sourcez votre installation ROS 2 pour que les commandes ros2 fonctionnent.

Naviguez dans le répertoire ros2_ws créé dans un tutoriel précédent.

Rappelez-vous que les packages doivent être créés dans le répertoire src, et non à la racine de l’espace de travail. Alors, naviguez dans ros2_ws/src et exécutez la commande de création de package :

ros2 pkg create --build-type ament_python py_pubsub

Votre terminal renverra un message vérifiant la création de votre package py_pubsub et tous ses fichiers et dossiers nécessaires.

2 Écrivez le nœud de l’éditeur

Accédez à ros2_ws/src/py_pubsub/py_pubsub. Rappelez-vous que ce répertoire est un package Python avec le même nom que le package ROS 2 dans lequel il est imbriqué.

Téléchargez l’exemple de code Talker en saisissant la commande suivante :

wget https://raw.githubusercontent.com/ros2/examples/rolling/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py

Il y aura maintenant un nouveau fichier nommé publisher_member_function.py adjacent à __init__.py.

Ouvrez le fichier à l’aide de votre éditeur de texte préféré.

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()

2.1 Examiner le code

Les premières lignes de code après les commentaires importent rclpy afin que sa classe Node puisse être utilisée.

import rclpy
from rclpy.node import Node

L’instruction suivante importe le type de message de chaîne intégré que le nœud utilise pour structurer les données qu’il transmet sur le sujet.

from std_msgs.msg import String

Ces lignes représentent les dépendances du nœud. Rappelez-vous que les dépendances doivent être ajoutées à package.xml, ce que vous ferez dans la section suivante.

Ensuite, la classe MinimalPublisher est créée, qui hérite de (ou est une sous-classe de) Node.

class MinimalPublisher(Node):

Voici la définition du constructeur de la classe. super().__init__ appelle le constructeur de la classe Node et lui donne le nom de votre nœud, dans ce cas minimal_publisher.

create_publisher déclare que le nœud publie des messages de type String (importés du module std_msgs.msg), sur un sujet nommé topic, et que la « taille de la file d’attente » est 10. La taille de la file d’attente est un paramètre QoS (qualité de service) requis qui limite la quantité de messages en file d’attente si un abonné ne les reçoit pas assez rapidement.

Ensuite, une minuterie est créée avec un rappel à exécuter toutes les 0,5 secondes. self.i est un compteur utilisé dans le rappel.

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

timer_callback crée un message avec la valeur du compteur ajoutée et le publie sur la console avec get_logger().info.

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

Enfin, la fonction principale est définie.

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()

La bibliothèque rclpy est d’abord initialisée, puis le nœud est créé, puis il « fait tourner » le nœud pour que ses rappels soient appelés.

2.2 Ajouter des dépendances

Naviguez d’un niveau vers le répertoire ros2_ws/src/py_pubsub, où les fichiers setup.py, setup.cfg et package.xml ont été créés pour vous.

Ouvrez package.xml avec votre éditeur de texte.

Comme mentionné dans le tutoriel précédent, assurez-vous de remplir les champs <description>, <maintainer> et < licence> balises :

<description>Examples of minimal publisher/subscriber using rclpy</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

Après les lignes ci-dessus, ajoutez les dépendances suivantes correspondant aux instructions d’importation de votre nœud :

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>

Cela déclare que le paquet a besoin de rclpy et std_msgs lorsque son code est exécuté.

Assurez-vous de sauvegarder le fichier.

2.3 Ajouter un point d’entrée

Ouvrez le fichier setup.py. Encore une fois, faites correspondre les champs maintainer, maintainer_email, description et license à votre package.xml :

maintainer='YourName',
maintainer_email='you@email.com',
description='Examples of minimal publisher/subscriber using rclpy',
license='Apache License 2.0',

Ajoutez la ligne suivante entre les parenthèses console_scripts du champ entry_points :

entry_points={
        'console_scripts': [
                'talker = py_pubsub.publisher_member_function:main',
        ],
},

N’oubliez pas de sauvegarder.

2.4 Vérifier setup.cfg

Le contenu du fichier setup.cfg doit être correctement rempli automatiquement, comme ceci :

[develop]
script_dir=$base/lib/py_pubsub
[install]
install_scripts=$base/lib/py_pubsub

Cela indique simplement à setuptools de mettre vos exécutables dans lib, car ros2 run les cherchera là-bas.

Vous pouvez créer votre package maintenant, sourcer les fichiers d’installation locaux et l’exécuter, mais créons d’abord le nœud d’abonné afin que vous puissiez voir le système complet au travail.

3 Écrivez le nœud de l’abonné

Retournez à ros2_ws/src/py_pubsub/py_pubsub pour créer le nœud suivant. Entrez le code suivant dans votre terminal :

wget https://raw.githubusercontent.com/ros2/examples/rolling/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py

Maintenant, le répertoire devrait contenir ces fichiers :

__init__.py  publisher_member_function.py  subscriber_member_function.py

3.1 Examiner le code

Ouvrez le subscriber_member_function.py avec votre éditeur de texte.

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()

Le code du nœud d’abonné est presque identique à celui de l’éditeur. Le constructeur crée un abonné avec les mêmes arguments que l’éditeur. Rappelez-vous du tutoriel topics que le nom du sujet et le type de message utilisés par l’éditeur et l’abonné doivent correspondre pour leur permettre communiquer.

self.subscription = self.create_subscription(
    String,
    'topic',
    self.listener_callback,
    10)

Le constructeur et le rappel de l’abonné n’incluent aucune définition de minuterie, car il n’en a pas besoin. Son rappel est appelé dès qu’il reçoit un message.

La définition de rappel imprime simplement un message d’information sur la console, ainsi que les données qu’elle a reçues. Rappelons que l’éditeur définit msg.data = 'Hello World: %d' % self.i

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

La définition principale est presque exactement la même, remplaçant la création et la rotation de l’éditeur par l’abonné.

minimal_subscriber = MinimalSubscriber()

rclpy.spin(minimal_subscriber)

Étant donné que ce nœud a les mêmes dépendances que l’éditeur, il n’y a rien de nouveau à ajouter à package.xml. Le fichier setup.cfg peut également rester intact.

3.2 Ajouter un point d’entrée

Rouvrez setup.py et ajoutez le point d’entrée du nœud d’abonné sous le point d’entrée de l’éditeur. Le champ entry_points devrait maintenant ressembler à ceci :

entry_points={
        'console_scripts': [
                'talker = py_pubsub.publisher_member_function:main',
                'listener = py_pubsub.subscriber_member_function:main',
        ],
},

Assurez-vous d’enregistrer le fichier, puis votre système pub/sub devrait être prêt à l’emploi.

4 Construire et exécuter

Vous avez probablement déjà les packages rclpy et std_msgs installés dans le cadre de votre système ROS 2. Il est recommandé d’exécuter rosdep à la racine de votre espace de travail (ros2_ws) pour vérifier les dépendances manquantes avant de compiler :

rosdep install -i --from-path src --rosdistro rolling -y

Toujours à la racine de votre espace de travail, ros2_ws, construisez votre nouveau package :

colcon build --packages-select py_pubsub

Ouvrez un nouveau terminal, accédez à ros2_ws et sourcez les fichiers d’installation :

. install/setup.bash

Exécutez maintenant le nœud Talker :

ros2 run py_pubsub talker

Le terminal devrait commencer à publier des messages d’information toutes les 0,5 seconde, comme ceci :

[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"
...

Ouvrez un autre terminal, sourcez à nouveau les fichiers de configuration depuis ros2_ws, puis démarrez le nœud d’écoute :

ros2 run py_pubsub listener

L’écouteur commencera à imprimer des messages sur la console, à partir du nombre de messages sur lequel l’éditeur se trouve à ce moment-là, comme ceci :

[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"

Entrez Ctrl+C dans chaque terminal pour empêcher les nœuds de tourner.

Résumé

Vous avez créé deux nœuds pour publier et vous abonner à des données sur une rubrique. Avant de les exécuter, vous avez ajouté leurs dépendances et leurs points d’entrée aux fichiers de configuration du package.

Prochaines étapes

Ensuite, vous allez créer un autre package ROS 2 simple en utilisant le modèle service/client. Encore une fois, vous pouvez choisir de l’écrire en C++ ou Python.