Implémentation d’interfaces personnalisées

Objectif : Découvrir d’autres façons d’implémenter des interfaces personnalisées dans ROS 2

Niveau du tutoriel : Débutant

Durée : 15 minutes

Arrière-plan

Dans un tutoriel précédent, vous avez appris à créer des interfaces msg et srv personnalisées.

Bien que la meilleure pratique consiste à déclarer les interfaces dans des packages d’interface dédiés, il peut parfois être pratique de déclarer, de créer et d’utiliser une interface dans un seul package.

Rappelez-vous que les interfaces ne peuvent actuellement être définies que dans les packages CMake. Il est cependant possible d’avoir des bibliothèques et des nœuds Python dans les packages CMake (en utilisant ament_cmake_python), vous pouvez donc définir des interfaces et des nœuds Python ensemble dans un seul paquet. Nous utiliserons ici un package CMake et des nœuds C++ par souci de simplicité.

Ce didacticiel se concentrera sur le type d’interface msg, mais les étapes ici sont applicables à tous les types d’interface.

Conditions préalables

Nous supposons que vous avez passé en revue les bases du didacticiel Création de fichiers msg et srv personnalisés avant de travailler sur celui-ci.

Vous devez avoir ROS 2 installé, un workspace et une compréhension de :doc :création de packages.

Comme toujours, n’oubliez pas de source ROS 2 dans chaque nouveau terminal que vous ouvrez.

Tâches

1 Créer un package

Dans le répertoire src de votre espace de travail, créez un package more_interfaces et créez-y un dossier pour les fichiers msg :

ros2 pkg create --build-type ament_cmake more_interfaces
mkdir more_interfaces/msg

2 Créer un fichier msg

Dans more_interfaces/msg, créez un nouveau fichier AddressBook.msg

Collez le code suivant pour créer un message destiné à transmettre des informations sur un individu :

bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address

Ce message est composé de 5 champs :

  • prénom : de type chaîne

  • nom_de_famille : de type chaîne

  • genre : de type bool, qui peut être soit MALE soit FEMALE

  • âge : de type uint8

  • adresse : de type chaîne

Notez qu’il est possible de définir des valeurs par défaut pour les champs dans la définition du message. Voir À propos des interfaces ROS 2 pour plus de façons de personnaliser les interfaces.

Ensuite, nous devons nous assurer que le fichier msg est transformé en code source pour C++, Python et d’autres langages.

2.1 Construire un fichier msg

Ouvrez package.xml et ajoutez les lignes suivantes :

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

Notez qu’au moment de la construction, nous avons besoin de rosidl_default_generators, tandis qu’au moment de l’exécution, nous n’avons besoin que de rosidl_default_runtime.

Ouvrez CMakeLists.txt et ajoutez les lignes suivantes :

Recherchez le package qui génère le code de message à partir des fichiers msg/srv :

find_package(rosidl_default_generators REQUIRED)

Déclarez la liste des messages que vous souhaitez générer :

set(msg_files
  "msg/AddressBook.msg"
)

En ajoutant les fichiers .msg manuellement, nous nous assurons que CMake sait quand il doit reconfigurer le projet après avoir ajouté d’autres fichiers .msg.

Générez les messages :

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

Assurez-vous également d’exporter la dépendance d’exécution du message :

ament_export_dependencies(rosidl_default_runtime)

Vous êtes maintenant prêt à générer des fichiers source à partir de votre définition msg. Nous allons ignorer l’étape de compilation pour l’instant car nous le faisons tous ensemble ci-dessous à l’étape 4.

2.2 (Extra) Définir plusieurs interfaces

Note

Vous pouvez utiliser set pour lister soigneusement toutes vos interfaces :

set(msg_files
  "msg/Message1.msg"
  "msg/Message2.msg"
  # etc
  )

set(srv_files
  "srv/Service1.srv"
  "srv/Service2.srv"
   # etc
  )

Et générez toutes les listes à la fois comme ceci :

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  ${srv_files}
)

3 Utiliser une interface du même package

Nous pouvons maintenant commencer à écrire du code qui utilise ce message.

Dans more_interfaces/src créez un fichier appelé publish_address_book.cpp et collez le code suivant :

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp"

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<more_interfaces::msg::AddressBook>("address_book", 10);

    auto publish_msg = [this]() -> void {
        auto message = more_interfaces::msg::AddressBook();

        message.first_name = "John";
        message.last_name = "Doe";
        message.age = 30;
        message.gender = message.MALE;
        message.address = "unknown";

        std::cout << "Publishing Contact\nFirst:" << message.first_name <<
          "  Last:" << message.last_name << std::endl;

        this->address_book_publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(1s, publish_msg);
  }

private:
  rclcpp::Publisher<more_interfaces::msg::AddressBook>::SharedPtr address_book_publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
};


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

  return 0;
}

3.1 Le code expliqué

#include "more_interfaces/msg/address_book.hpp"

Incluez l’en-tête de notre nouveau AddressBook.msg.

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<more_interfaces::msg::AddressBook>("address_book");

Créez un nœud et un éditeur AddressBook.

auto publish_msg = [this]() -> void {

Créez un rappel pour publier périodiquement les messages.

auto message = more_interfaces::msg::AddressBook();

Créez une instance de message AddressBook que nous publierons plus tard.

message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";

Remplir les champs Carnet d'adresses.

std::cout << "Publishing Contact\nFirst:" << message.first_name <<
  "  Last:" << message.last_name << std::endl;

this->address_book_publisher_->publish(message);

Enfin, envoyez le message périodiquement.

timer_ = this->create_wall_timer(1s, publish_msg);

Créez un minuteur de 1 seconde pour appeler notre fonction publish_msg toutes les secondes.

3.2 Créer l’éditeur

Nous devons créer une nouvelle cible pour ce nœud dans le CMakeLists.txt :

find_package(rclcpp REQUIRED)

add_executable(publish_address_book
  src/publish_address_book.cpp
)

ament_target_dependencies(publish_address_book
  "rclcpp"
)

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

4 Essayez-le

Revenez à la racine de l’espace de travail pour créer le package :

cd ~/ros2_ws
colcon build --packages-up-to more_interfaces

Sourcez ensuite l’espace de travail et exécutez l’éditeur :

. install/local_setup.bash
ros2 run more_interfaces publish_address_book

Vous devriez voir l’éditeur relayer le message que vous avez défini, y compris les valeurs que vous avez définies dans publish_address_book.cpp.

Pour confirmer que le message est publié sur le sujet address_book, ouvrez un autre terminal, sourcez l’espace de travail et appelez topic echo :

. install/setup.bash
ros2 topic echo /address_book

Nous ne créerons pas d’abonné dans ce didacticiel, mais vous pouvez essayer d’en écrire un vous-même pour vous entraîner (utilisez Écrire un simple éditeur et abonné (C++) pour vous aider).

5 (Extra) Utiliser une définition d’interface existante

Note

Vous pouvez utiliser une définition d’interface existante dans une nouvelle définition d’interface. Par exemple, supposons qu’il existe un message nommé Contact.msg qui appartient à un package ROS 2 existant nommé rosidl_tutorials_msgs. Supposons que sa définition soit identique à notre interface personnalisée AddressBook.msg de la version précédente.

Dans ce cas, vous auriez pu définir AddressBook.msg (une interface dans le package avec vos nœuds) comme type Contact (une interface dans un package séparé). Vous pouvez même définir AddressBook.msg comme un tableau de type Contact, comme ceci :

rosidl_tutorials_msgs/Contact[] address_book

Pour générer ce message, vous devez déclarer une dépendance au package Contact.msg's, rosidl_tutorials_msgs, dans package.xml :

<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>

Et dans CMakeLists.txt :

find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  DEPENDENCIES rosidl_tutorials_msgs
)

Vous devrez également inclure l’en-tête de Contact.msg dans votre nœud d’éditeur afin de pouvoir ajouter contacts à votre carnet d'adresses.

#include "rosidl_tutorials_msgs/msg/contact.hpp"

Vous pouvez modifier l’appel en quelque chose comme ceci :

auto publish_msg = [this]() -> void {
   auto msg = std::make_shared<more_interfaces::msg::AddressBook>();
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "John";
     contact.last_name = "Doe";
     contact.age = 30;
     contact.gender = contact.MALE;
     contact.address = "unknown";
     msg->address_book.push_back(contact);
   }
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "Jane";
     contact.last_name = "Doe";
     contact.age = 20;
     contact.gender = contact.FEMALE;
     contact.address = "unknown";
     msg->address_book.push_back(contact);
   }

   std::cout << "Publishing address book:" << std::endl;
   for (auto contact : msg->address_book) {
     std::cout << "First:" << contact.first_name << "  Last:" << contact.last_name <<
       std::endl;
   }

   address_book_publisher_->publish(*msg);
 };

La construction et l’exécution de ces modifications afficheraient le msg défini comme prévu, ainsi que le tableau de msgs défini ci-dessus.

Résumé

Dans ce didacticiel, vous avez essayé différents types de champs pour définir des interfaces, puis créé une interface dans le même package où elle est utilisée.

Vous avez également appris à utiliser une autre interface comme type de champ, ainsi que les instructions package.xml, CMakeLists.txt et #include nécessaires à l’utilisation de cette fonctionnalité.

Prochaines étapes

Ensuite, vous allez créer un package ROS 2 simple avec un paramètre personnalisé que vous apprendrez à définir à partir d’un fichier de lancement. Encore une fois, vous pouvez choisir de l’écrire en C++ ou Python.