Écrire un simple éditeur et abonné (C++)

Objectif : créer et exécuter un nœud d’éditeur et d’abonné à l’aide de C++.

Niveau du tutoriel : Débutant

Durée : 20 minutes

Arrière-plan

Nodes sont des processus exécutables qui communiquent via le graphe ROS. Dans ce didacticiel, les nœuds se transmettront des informations sous forme de messages de chaîne via un topic. 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.

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_cmake cpp_pubsub

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

Accédez à ros2_ws/src/cpp_pubsub/src. Rappelez-vous qu’il s’agit du répertoire de tout package CMake auquel appartiennent les fichiers source contenant les exécutables.

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

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

wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_publisher/member_function.cpp

Il y aura maintenant un nouveau fichier nommé publisher_member_function.cpp. Ouvrez le fichier à l’aide de votre éditeur de texte préféré.

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = "Hello, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
      publisher_->publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

2.1 Examiner le code

La partie supérieure du code inclut les en-têtes C++ standard que vous utiliserez. Après les en-têtes C++ standard se trouve l’inclusion rclcpp/rclcpp.hpp qui vous permet d’utiliser les éléments les plus courants du système ROS 2. Le dernier est std_msgs/msg/string.hpp, qui inclut le type de message intégré que vous utiliserez pour publier des données.

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

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

La ligne suivante crée la classe de nœud MinimalPublisher en héritant de rclcpp::Node. Chaque this dans le code fait référence au nœud.

class MinimalPublisher : public rclcpp::Node

Le constructeur public nomme le nœud minimal_publisher et initialise count_ à 0. À l’intérieur du constructeur, l’éditeur est initialisé avec le type de message String, le nom de sujet topic et le taille de file d’attente requise pour limiter les messages en cas de sauvegarde. Ensuite, timer_ est initialisé, ce qui provoque l’exécution de la fonction timer_callback deux fois par seconde.

public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
    500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

La fonction timer_callback est l’endroit où les données du message sont définies et les messages sont réellement publiés. La macro RCLCPP_INFO garantit que chaque message publié est imprimé sur la console.

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }

La dernière est la déclaration des champs timer, publisher et counter.

rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;

Après la classe MinimalPublisher se trouve main, où le nœud s’exécute réellement. rclcpp::init initialise ROS 2 et rclcpp::spin commence à traiter les données du nœud, y compris les rappels du temporisateur.

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

2.2 Ajouter des dépendances

Naviguez d’un niveau vers le répertoire ros2_ws/src/cpp_pubsub, où les fichiers CMakeLists.txt 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 rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

Ajoutez une nouvelle ligne après la dépendance buildtool ament_cmake et collez les dépendances suivantes correspondant aux instructions d’inclusion de votre nœud :

<depend>rclcpp</depend>
<depend>std_msgs</depend>

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

Assurez-vous de sauvegarder le fichier.

2.3 CMakeLists.txt

Ouvrez maintenant le fichier CMakeLists.txt. Sous la dépendance existante find_package(ament_cmake REQUIRED), ajoutez les lignes :

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

Après cela, ajoutez l’exécutable et nommez-le talker afin que vous puissiez exécuter votre nœud en utilisant ros2 run :

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

Enfin, ajoutez la section install(TARGETS…) pour que ros2 run puisse trouver votre exécutable :

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

Vous pouvez nettoyer votre CMakeLists.txt en supprimant certaines sections et commentaires inutiles, il ressemble donc à ceci :

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

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

ament_package()

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/cpp_pubsub/src pour créer le nœud suivant. Entrez le code suivant dans votre terminal :

wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_subscriber/member_function.cpp

Saisir ls dans la console renverra maintenant :

publisher_member_function.cpp  subscriber_member_function.cpp

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

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String & msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

3.1 Examiner le code

Le code du nœud d’abonné est presque identique à celui de l’éditeur. Maintenant, le nœud est nommé minimal_subscriber, et le constructeur utilise la classe create_subscription du nœud pour exécuter le rappel.

Il n’y a pas de temporisateur car l’abonné répond simplement chaque fois que des données sont publiées dans le sujet sujet.

public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

Rappelez-vous du tutoriel topic que le nom du sujet et le type de message utilisés par l’éditeur et l’abonné doivent correspondre pour leur permettre communiquer.

La fonction topic_callback reçoit la chaîne de données du message publiée sur le sujet et l’écrit simplement dans la console à l’aide de la macro RCLCPP_INFO.

La seule déclaration de champ dans cette classe est la souscription.

private:
  void topic_callback(const std_msgs::msg::String & msg) const
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

La fonction main est exactement la même, sauf qu’elle fait tourner le nœud MinimalSubscriber. Pour le nœud de l’éditeur, tourner signifiait démarrer le minuteur, mais pour l’abonné, cela signifie simplement se préparer à recevoir des messages chaque fois qu’ils arrivent.

Étant donné que ce nœud a les mêmes dépendances que le nœud de l’éditeur, il n’y a rien de nouveau à ajouter à package.xml.

3.2 CMakeLists.txt

Rouvrez CMakeLists.txt et ajoutez l’exécutable et la cible du nœud d’abonné sous les entrées de l’éditeur.

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

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 rclcpp 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 cpp_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 cpp_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 cpp_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 compiler et de les exécuter, vous avez ajouté leurs dépendances et leurs exécutables 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.