Écrire un service et un client simples (Python)

Objectif : créer et exécuter des nœuds de service et de client à l’aide de Python.

Niveau du tutoriel : Débutant

Durée : 20 minutes

Arrière-plan

Lorsque nodes communiquent en utilisant services, le nœud qui envoie une demande de données est appelé le nœud client, et celui qui répond à la demande est le nœud de service. La structure de la requête et de la réponse est déterminée par un fichier .srv.

L’exemple utilisé ici est un simple système d’addition d’entiers ; un nœud demande la somme de deux entiers et l’autre répond avec le résultat.

Conditions préalables

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

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. Accédez à ros2_ws/src et créez un nouveau package :

ros2 pkg create --build-type ament_python py_srvcli --dependencies rclpy example_interfaces

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

L’argument --dependencies ajoutera automatiquement les lignes de dépendance nécessaires à package.xml. example_interfaces est le package qui inclut le fichier .srv dont vous aurez besoin pour structurer vos requêtes et réponses :

int64 a
int64 b
---
int64 sum

Les deux premières lignes sont les paramètres de la requête, et sous les tirets se trouve la réponse.

1.1 Mettre à jour package.xml

Comme vous avez utilisé l’option --dependencies lors de la création du package, vous n’avez pas besoin d’ajouter manuellement des dépendances à package.xml.

Comme toujours, assurez-vous d’ajouter la description, l’adresse e-mail et le nom du responsable, ainsi que les informations de licence à package.xml.

<description>Python client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

1.2 Mettre à jour setup.py

Ajoutez les mêmes informations au fichier setup.py pour les champs maintainer, maintainer_email, description et license :

maintainer='Your Name',
maintainer_email='you@email.com',
description='Python client server tutorial',
license='Apache License 2.0',

2 Écrivez le nœud de service

Dans le répertoire ros2_ws/src/py_srvcli/py_srvcli, créez un nouveau fichier appelé service_member_function.py et collez le code suivant à l’intérieur :

from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node


class MinimalService(Node):

    def __init__(self):
        super().__init__('minimal_service')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

        return response


def main():
    rclpy.init()

    minimal_service = MinimalService()

    rclpy.spin(minimal_service)

    rclpy.shutdown()


if __name__ == '__main__':
    main()

2.1 Examiner le code

La première instruction import importe le type de service AddTwoInts du paquet example_interfaces. L’instruction import suivante importe la bibliothèque client ROS 2 Python, et plus particulièrement la classe Node.

from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

Le constructeur de la classe MinimalService initialise le nœud avec le nom minimal_service. Ensuite, il crée un service et définit le type, le nom et le rappel.

def __init__(self):
    super().__init__('minimal_service')
    self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

La définition du rappel de service reçoit les données de la demande, les additionne et renvoie la somme en réponse.

def add_two_ints_callback(self, request, response):
    response.sum = request.a + request.b
    self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

    return response

Enfin, la classe principale initialise la bibliothèque client Python ROS 2, instancie la classe MinimalService pour créer le nœud de service et fait tourner le nœud pour gérer les rappels.

2.2 Ajouter un point d’entrée

Pour permettre à la commande ros2 run d’exécuter votre nœud, vous devez ajouter le point d’entrée à setup.py (situé dans le répertoire ros2_ws/src/py_srvcli).

Ajoutez la ligne suivante entre les crochets 'console_scripts': :

'service = py_srvcli.service_member_function:main',

3 Écrivez le nœud client

Dans le répertoire ros2_ws/src/py_srvcli/py_srvcli, créez un nouveau fichier appelé client_member_function.py et collez le code suivant à l’intérieur :

import sys

from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node


class MinimalClientAsync(Node):

    def __init__(self):
        super().__init__('minimal_client_async')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()

    def send_request(self, a, b):
        self.req.a = a
        self.req.b = b
        self.future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()


def main():
    rclpy.init()

    minimal_client = MinimalClientAsync()
    response = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
    minimal_client.get_logger().info(
        'Result of add_two_ints: for %d + %d = %d' %
        (int(sys.argv[1]), int(sys.argv[2]), response.sum))

    minimal_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

3.1 Examiner le code

La seule instruction import différente pour le client est import sys. Le code du nœud client utilise sys.argv pour accéder aux arguments d’entrée de la ligne de commande pour la requête.

La définition du constructeur crée un client avec le même type et le même nom que le nœud de service. Le type et le nom doivent correspondre pour que le client et le service puissent communiquer.

La boucle while dans le constructeur vérifie si un service correspondant au type et au nom du client est disponible une fois par seconde.

Sous le constructeur se trouve la définition de la requête, suivie de main.

La seule différence significative dans le main du client est la boucle while. La boucle vérifie le futur pour voir s’il y a une réponse du service, tant que le système est en cours d’exécution. Si le service a envoyé une réponse, le résultat sera écrit dans un message de journal.

3.2 Ajouter un point d’entrée

Comme le nœud de service, vous devez également ajouter un point d’entrée pour pouvoir exécuter le nœud client.

Le champ entry_points de votre fichier setup.py devrait ressembler à ceci :

entry_points={
    'console_scripts': [
        'service = py_srvcli.service_member_function:main',
        'client = py_srvcli.client_member_function:main',
    ],
},

4 Construire et exécuter

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

Revenez à la racine de votre espace de travail, ros2_ws, et créez votre nouveau package :

colcon build --packages-select py_srvcli

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

. install/setup.bash

Exécutez maintenant le nœud de service :

ros2 run py_srvcli service

Le nœud attendra la demande du client.

Ouvrez un autre terminal et sourcez à nouveau les fichiers de configuration depuis ros2_ws. Démarrez le nœud client, suivi de deux nombres entiers séparés par un espace :

ros2 run py_srvcli client 2 3

Si vous choisissez 2 et 3, par exemple, le client recevra une réponse comme celle-ci :

[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5

Revenez au terminal sur lequel votre nœud de service est en cours d’exécution. Vous verrez qu’il a publié des messages de journal lorsqu’il a reçu la demande :

[INFO] [minimal_service]: Incoming request
a: 2 b: 3

Entrez Ctrl+C dans le terminal du serveur pour empêcher le nœud de tourner.

Résumé

Vous avez créé deux nœuds pour demander et répondre aux données sur un service. Vous avez ajouté leurs dépendances et leurs exécutables aux fichiers de configuration du package afin de pouvoir les créer et les exécuter, ce qui vous permet de voir un système service/client au travail.

Prochaines étapes

Dans les derniers didacticiels, vous avez utilisé des interfaces pour transmettre des données entre des sujets et des services. Ensuite, vous apprendrez à créer des interfaces personnalisées.