Ecrire un service et un client simples (C++)

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

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_cmake cpp_srvcli --dependencies rclcpp example_interfaces

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

L’argument --dependencies ajoutera automatiquement les lignes de dépendance nécessaires à package.xml et CMakeLists.txt. 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 ou CMakeLists.txt.

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>C++ client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

2 Écrivez le nœud de service

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

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <memory>

void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
          std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>      response)
{
  response->sum = request->a + request->b;
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
                request->a, request->b);
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
    node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);

  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");

  rclcpp::spin(node);
  rclcpp::shutdown();
}

2.1 Examiner le code

Les deux premières déclarations #include sont vos dépendances de paquet.

La fonction add ajoute deux entiers à partir de la requête et donne la somme à la réponse, tout en notifiant la console de son état à l’aide de journaux.

void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
         std::shared_ptr<example_interfaces::srv::AddTwoInts::Response>      response)
{
    response->sum = request->a + request->b;
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
        request->a, request->b);
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

La fonction main accomplit ce qui suit, ligne par ligne :

  • Initialise la bibliothèque cliente ROS 2 C++ :

    rclcpp::init(argc, argv);
    
  • Crée un nœud nommé add_two_ints_server :

    std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
    
  • Crée un service nommé add_two_ints pour ce nœud et l’annonce automatiquement sur les réseaux avec la méthode &add :

    rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
    node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
    
  • Imprime un message de journal lorsqu’il est prêt :

    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
    
  • Fait tourner le nœud, rendant le service disponible.

    rclcpp::spin(node);
    

2.2 Ajouter un exécutable

La macro add_executable génère un exécutable que vous pouvez exécuter en utilisant ros2 run. Ajoutez le bloc de code suivant à CMakeLists.txt pour créer un exécutable nommé server :

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)

Ainsi, ros2 run peut trouver l’exécutable, ajoutez les lignes suivantes à la fin du fichier, juste avant ament_package() :

install(TARGETS
  server
  DESTINATION lib/${PROJECT_NAME})

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 client afin que vous puissiez voir le système complet au travail.

3 Écrivez le nœud client

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

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <chrono>
#include <cstdlib>
#include <memory>

using namespace std::chrono_literals;

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

  if (argc != 3) {
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
      return 1;
  }

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
    node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

  auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
  request->a = atoll(argv[1]);
  request->b = atoll(argv[2]);

  while (!client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
  }

  auto result = client->async_send_request(request);
  // Wait for the result.
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
  } else {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
  }

  rclcpp::shutdown();
  return 0;
}

3.1 Examiner le code

Comme pour le nœud de service, les lignes de code suivantes créent le nœud, puis créent le client pour ce nœud :

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
  node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

Ensuite, la demande est créée. Sa structure est définie par le fichier .srv mentionné précédemment.

auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);

La boucle while donne au client 1 seconde pour rechercher des nœuds de service dans le réseau. S’il n’en trouve pas, il continuera d’attendre.

RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");

Si le client est annulé (par exemple en saisissant Ctrl+C dans le terminal), il renverra un message d’erreur indiquant qu’il a été interrompu.

RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
  return 0;

Ensuite, le client envoie sa demande et le nœud tourne jusqu’à ce qu’il reçoive sa réponse ou échoue.

3.2 Ajouter un exécutable

Retournez à CMakeLists.txt pour ajouter l’exécutable et la cible du nouveau nœud. Après avoir supprimé certains passe-partout inutiles du fichier généré automatiquement, votre CMakeLists.txt devrait ressembler à ceci :

cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
  rclcpp example_interfaces)

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp example_interfaces)

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})

ament_package()

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 cpp_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 cpp_srvcli server

Le terminal doit renvoyer le message suivant, puis patienter :

[INFO] [rclcpp]: Ready to add two ints.

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

ros2 run cpp_srvcli client 2 3

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

[INFO] [rclcpp]: Sum: 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 et les données qu’il a reçues, et la réponse qu’il a renvoyée :

[INFO] [rclcpp]: Incoming request
a: 2 b: 3
[INFO] [rclcpp]: sending back response: [5]

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, et 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.