Création de fichiers msg et srv personnalisés

Objectif : Définir des fichiers d’interface personnalisés (.msg et .srv) et les utiliser avec les nœuds Python et C++.

Niveau du tutoriel : Débutant

Durée : 20 minutes

Arrière-plan

Dans les didacticiels précédents, vous avez utilisé des interfaces de message et de service pour en savoir plus sur topics, services, et simple éditeur/abonné (C++/ Python) et service/client (C++/Python). Les interfaces que vous avez utilisées ont été prédéfinies dans ces cas.

Bien qu’il soit recommandé d’utiliser des définitions d’interface prédéfinies, vous devrez probablement également définir vos propres messages et services. Ce didacticiel vous présentera la méthode la plus simple de création de définitions d’interface personnalisées.

Conditions préalables

Vous devriez avoir un espace de travail ROS 2.

Ce tutoriel utilise également les packages créés dans l’éditeur/abonné (C++ et Python) et service/client (C++ et Python) tutoriels pour essayer les nouveaux messages personnalisés.

Tâches

1 Créer un nouveau package

Pour ce didacticiel, vous allez créer des fichiers personnalisés .msg et .srv dans leur propre package, puis les utiliser dans un package séparé. Les deux packages doivent se trouver dans le même espace de travail.

Puisque nous allons utiliser les packages pub/sub et service/client créés dans les tutoriels précédents, assurez-vous que vous êtes dans le même espace de travail que ces packages (ros2_ws/src), puis exécutez la commande suivante pour créer un nouveau package :

ros2 pkg create --build-type ament_cmake tutorial_interfaces

tutorial_interfaces est le nom du nouveau paquet. Notez qu’il s’agit d’un package CMake ; il n’existe actuellement aucun moyen de générer un fichier .msg ou .srv dans un package Python pur. Vous pouvez créer une interface personnalisée dans un package CMake, puis l’utiliser dans un nœud Python, qui sera traité dans la dernière section.

Les fichiers .msg et .srv doivent être placés dans des répertoires appelés respectivement msg et srv. Créez les répertoires dans ros2_ws/src/tutorial_interfaces :

mkdir msg

mkdir srv

2 Créer des définitions personnalisées

2.1 définition des messages

Dans le répertoire tutorial_interfaces/msg que vous venez de créer, créez un nouveau fichier appelé Num.msg avec une ligne de code déclarant sa structure de données :

int64 num

Il s’agit d’un message personnalisé qui transfère un seul entier 64 bits appelé num.

Toujours dans le répertoire tutorial_interfaces/msg que vous venez de créer, créez un nouveau fichier appelé Sphere.msg avec le contenu suivant :

geometry_msgs/Point center
float64 radius

Ce message personnalisé utilise un message d’un autre package de message (geometry_msgs/Point dans ce cas).

2.2 définition srv

De retour dans le répertoire tutorial_interfaces/srv que vous venez de créer, créez un nouveau fichier appelé AddThreeInts.srv avec la structure de requête et de réponse suivante :

int64 a
int64 b
int64 c
---
int64 sum

Il s’agit de votre service personnalisé qui demande trois entiers nommés a, b et c, et répond avec un entier appelé sum.

3 CMakeLists.txt

Pour convertir les interfaces que vous avez définies en code spécifique à un langage (comme C++ et Python) afin qu’elles puissent être utilisées dans ces langages, ajoutez les lignes suivantes à CMakeLists.txt :

find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Num.msg"
  "msg/Sphere.msg"
  "srv/AddThreeInts.srv"
  DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)

Note

Le nom de la bibliothèque doit correspondre à ${PROJECT_NAME} (voir https://github.com/ros2/rosidl/issues/441#issuecomment-591025515).

4 paquet.xml

Parce que les interfaces s’appuient sur rosidl_default_generators pour générer du code spécifique au langage, vous devez déclarer une dépendance sur celui-ci. La balise <exec_depend> est utilisée pour spécifier les dépendances d’exécution ou d’étape d’exécution et rosidl_interface_packages est le nom du groupe de dépendances auquel appartient le package, déclaré à l’aide de la balise <member_of_group> .

Ajoutez les lignes suivantes à package.xml

<depend>geometry_msgs</depend>

<build_depend>rosidl_default_generators</build_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

5 Construisez le package tutorial_interfaces

Maintenant que toutes les parties de votre package d’interfaces personnalisées sont en place, vous pouvez créer le package. À la racine de votre espace de travail (~/ros2_ws), exécutez la commande suivante :

colcon build --packages-select tutorial_interfaces

Désormais, les interfaces seront détectables par d’autres packages ROS 2.

6 Confirmer la création de msg et srv

Dans un nouveau terminal, exécutez la commande suivante depuis votre espace de travail (ros2_ws) pour le source :

. install/setup.bash

Vous pouvez maintenant confirmer que la création de votre interface a fonctionné en utilisant la commande ros2 interface show :

ros2 interface show tutorial_interfaces/msg/Num

doit retourner :

int64 num

Et

ros2 interface show tutorial_interfaces/msg/Sphere

doit retourner :

geometry_msgs/Point center
        float64 x
        float64 y
        float64 z
float64 radius

Et

ros2 interface show tutorial_interfaces/srv/AddThreeInts

doit retourner :

int64 a
int64 b
int64 c
---
int64 sum

7 Testez les nouvelles interfaces

Pour cette étape, vous pouvez utiliser les packages que vous avez créés dans les didacticiels précédents. Quelques modifications simples sur les nœuds, les fichiers CMakeLists et package vous permettront d’utiliser vos nouvelles interfaces.

7.1 Tester Num.msg avec pub/sub

Avec quelques légères modifications du package éditeur/abonné créé dans un tutoriel précédent (C++ ou Python), vous pouvez voir Num.msg en action. Puisque vous allez changer la chaîne standard msg en une chaîne numérique, la sortie sera légèrement différente.

Éditeur:

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"                                            // CHANGE

using namespace std::chrono_literals;

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

private:
  void timer_callback()
  {
    auto message = tutorial_interfaces::msg::Num();                                   // CHANGE
    message.num = this->count_++;                                                     // CHANGE
    RCLCPP_INFO_STREAM(this->get_logger(), "Publishing: '" << message.num << "'");    // CHANGE
    publisher_->publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_;             // CHANGE
  size_t count_;
};

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

Abonné:

#include <functional>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp"                                       // CHANGE

using std::placeholders::_1;

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

private:
  void topic_callback(const tutorial_interfaces::msg::Num & msg) const  // CHANGE
  {
    RCLCPP_INFO_STREAM(this->get_logger(), "I heard: '" << msg.num << "'");     // CHANGE
  }
  rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_;  // CHANGE
};

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

CMakeLists.txt :

Ajoutez les lignes suivantes (C++ uniquement) :

#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)                      # CHANGE

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)    # CHANGE

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)  # CHANGE

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

ament_package()

package.xml :

Ajoutez la ligne suivante :

<depend>tutorial_interfaces</depend>

Après avoir effectué les modifications ci-dessus et enregistré toutes les modifications, créez le package :

colcon build --packages-select cpp_pubsub

Sous Windows :

colcon build --merge-install --packages-select cpp_pubsub

Ouvrez ensuite deux nouveaux terminaux, source ros2_ws dans chacun, et exécutez :

ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener

Étant donné que Num.msg ne relaie qu’un entier, le locuteur ne devrait publier que des valeurs entières, contrairement à la chaîne qu’il a publiée précédemment :

[INFO] [minimal_publisher]: Publishing: '0'
[INFO] [minimal_publisher]: Publishing: '1'
[INFO] [minimal_publisher]: Publishing: '2'

7.2 Tester AddThreeInts.srv avec service/client

Avec quelques légères modifications du package service/client créé dans un tutoriel précédent (C++ ou Python), vous pouvez voir AddThreeInts.srv en action. Étant donné que vous allez changer le srv de requête à deux entiers d’origine en un srv de requête à trois entiers, la sortie sera légèrement différente.

Service:

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp"                                        // CHANGE

#include <memory>

void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request,     // CHANGE
          std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response>       response)  // CHANGE
{
  response->sum = request->a + request->b + request->c;                                      // CHANGE
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld",  // CHANGE
                request->a, request->b, request->c);                                         // CHANGE
  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_three_ints_server");   // CHANGE

  rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service =               // CHANGE
    node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints",  &add);   // CHANGE

  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints.");                     // CHANGE

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

Client:

#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp"                                       // CHANGE

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

using namespace std::chrono_literals;

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

  if (argc != 4) { // CHANGE
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z");      // CHANGE
      return 1;
  }

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client");  // CHANGE
  rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client =                // CHANGE
    node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints");          // CHANGE

  auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>();       // CHANGE
  request->a = atoll(argv[1]);
  request->b = atoll(argv[2]);
  request->c = atoll(argv[3]);                                                              // CHANGE

  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_three_ints");    // CHANGE
  }

  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt :

Ajoutez les lignes suivantes (C++ uniquement) :

#...

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)         # CHANGE

add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
  rclcpp tutorial_interfaces)                      # CHANGE

add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp tutorial_interfaces)                      # CHANGE

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

ament_package()

package.xml :

Ajoutez la ligne suivante :

<depend>tutorial_interfaces</depend>

Après avoir effectué les modifications ci-dessus et enregistré toutes les modifications, créez le package :

colcon build --packages-select cpp_srvcli

Sous Windows :

colcon build --merge-install --packages-select cpp_srvcli

Ouvrez ensuite deux nouveaux terminaux, source ros2_ws dans chacun, et exécutez :

ros2 run cpp_srvcli server
ros2 run cpp_srvcli client 2 3 1

Résumé

Dans ce didacticiel, vous avez appris à créer des interfaces personnalisées dans leur propre package et à utiliser ces interfaces à partir d’autres packages.

Il s’agit d’une méthode simple de création et d’utilisation de l’interface. Vous pouvez en savoir plus sur les interfaces ici.

Prochaines étapes

Le tutoriel suivant couvre d’autres façons d’utiliser les interfaces dans ROS 2.