Libérer le potentiel du middleware Fast DDS [contribution de la communauté]
Objectif : Ce didacticiel vous montrera comment utiliser les capacités de configuration étendues de Fast DDS dans ROS 2.
Niveau du didacticiel : Avancé
Durée : 20 minutes
Table des matières
Arrière-plan
L’interface entre la pile ROS 2 et Fast DDS est fournie par l’implémentation du middleware ROS 2 rmw_fastrtps. Cette implémentation est disponible dans toutes les distributions ROS 2, à la fois des binaires et des sources.
ROS 2 RMW ne permet que la configuration de certains middleware QoS (voir Politiques de QoS ROS 2). Cependant, rmw_fastrtps
offre des capacités de configuration étendues pour tirer pleinement parti des fonctionnalités de Fast DDS. Ce didacticiel vous guidera à travers une série d’exemples expliquant comment utiliser des fichiers XML pour déverrouiller cette configuration étendue.
Afin d’obtenir plus d’informations sur l’utilisation de Fast DDS sur ROS 2, veuillez consulter la documentation suivante
__.
Conditions préalables
Ce didacticiel suppose que vous savez comment créer un package. Cela suppose également que vous savez écrire un simple publisher and subscriber et un simple service et client. Bien que les exemples soient implémentés en C++, les mêmes concepts s’appliquent aux packages Python.
Mélanger des publications synchrones et asynchrones dans un même nœud
Dans ce premier exemple, un nœud avec deux éditeurs, l’un avec un mode de publication synchrone et l’autre avec un mode de publication asynchrone, sera créé.
rmw_fastrtps
utilise le mode de publication asynchrone par défaut. Lorsque l’éditeur invoque l’opération d’écriture, les données sont copiées dans une file d’attente, un thread d’arrière-plan (thread asynchrone) est informé de l’ajout à la file d’attente et le contrôle du thread est rendu à l’utilisateur avant que les données ne soient réellement envoyées. Le thread d’arrière-plan est chargé de consommer la file d’attente et d’envoyer les données à chaque lecteur correspondant.
En revanche, avec le mode de publication synchrone, les données sont envoyées directement dans le contexte du thread utilisateur. Cela implique que tout appel bloquant survenant pendant l’opération d’écriture bloquerait le thread utilisateur, empêchant ainsi l’application de continuer son fonctionnement. Cependant, ce mode donne généralement des débits plus élevés à des latences plus faibles, car il n’y a pas de notification ni de changement de contexte entre les threads.
Créer le nœud avec les éditeurs
Tout d’abord, créez un nouveau package nommé sync_async_node_example_cpp
sur un nouvel espace de travail :
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs -- sync_async_node_example_cpp
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs -- sync_async_node_example_cpp
md \ros2_ws\src
cd \ros2_ws\src
ros2 pkg create --build-type ament_cmake --dependencies rclcpp std_msgs -- sync_async_node_example_cpp
Ensuite, ajoutez un fichier nommé src/sync_async_writer.cpp
au package, avec le contenu suivant. Notez que l’éditeur synchrone publiera sur le sujet sync_topic
, tandis que l’éditeur asynchrone publiera sur le sujet async_topic
.
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class SyncAsyncPublisher : public rclcpp::Node
{
public:
SyncAsyncPublisher()
: Node("sync_async_publisher"), count_(0)
{
// Create the synchronous publisher on topic 'sync_topic'
sync_publisher_ = this->create_publisher<std_msgs::msg::String>("sync_topic", 10);
// Create the asynchronous publisher on topic 'async_topic'
async_publisher_ = this->create_publisher<std_msgs::msg::String>("async_topic", 10);
// This timer will trigger the publication of new data every half a second
timer_ = this->create_wall_timer(
500ms, std::bind(&SyncAsyncPublisher::timer_callback, this));
}
private:
/**
* Actions to run every time the timer expires
*/
void timer_callback()
{
// Create a new message to be sent
auto sync_message = std_msgs::msg::String();
sync_message.data = "SYNC: Hello, world! " + std::to_string(count_);
// Log the message to the console to show progress
RCLCPP_INFO(this->get_logger(), "Synchronously publishing: '%s'", sync_message.data.c_str());
// Publish the message using the synchronous publisher
sync_publisher_->publish(sync_message);
// Create a new message to be sent
auto async_message = std_msgs::msg::String();
async_message.data = "ASYNC: Hello, world! " + std::to_string(count_);
// Log the message to the console to show progress
RCLCPP_INFO(this->get_logger(), "Asynchronously publishing: '%s'", async_message.data.c_str());
// Publish the message using the asynchronous publisher
async_publisher_->publish(async_message);
// Prepare the count for the next message
count_++;
}
// This timer will trigger the publication of new data every half a second
rclcpp::TimerBase::SharedPtr timer_;
// A publisher that publishes asynchronously
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr async_publisher_;
// A publisher that publishes synchronously
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr sync_publisher_;
// Number of messages sent so far
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SyncAsyncPublisher>());
rclcpp::shutdown();
return 0;
}
Ouvrez maintenant le fichier CMakeLists.txt
et ajoutez un nouvel exécutable et nommez-le SyncAsyncWriter
afin que vous puissiez exécuter votre nœud en utilisant ros2 run
:
add_executable(SyncAsyncWriter src/sync_async_writer.cpp)
ament_target_dependencies(SyncAsyncWriter rclcpp std_msgs)
Enfin, ajoutez la section install(TARGETS…)
pour que ros2 run
puisse trouver votre exécutable :
install(TARGETS
SyncAsyncWriter
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.8)
project(sync_async_node_example_cpp)
# 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(SyncAsyncWriter src/sync_async_writer.cpp)
ament_target_dependencies(SyncAsyncWriter rclcpp std_msgs)
install(TARGETS
SyncAsyncWriter
DESTINATION lib/${PROJECT_NAME})
ament_package()
Si ce nœud est créé et exécuté maintenant, les deux éditeurs se comporteront de la même manière, en publiant de manière asynchrone dans les deux rubriques, car il s’agit du mode de publication par défaut. La configuration par défaut du mode de publication peut être modifiée en runtime lors du lancement du nœud, à l’aide d’un fichier XML.
Créer le fichier XML avec la configuration du profil
Créez un fichier avec le nom SyncAsync.xml
et le contenu suivant :
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<!-- default publisher profile -->
<publisher profile_name="default_publisher" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</publisher>
<!-- publisher profile for topic sync_topic -->
<publisher profile_name="/sync_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>SYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
<!-- publisher profile for topic async_topic -->
<publisher profile_name="/async_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>ASYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
</profiles>
Notez que plusieurs profils d’éditeurs sont définis. Un profil par défaut qui est défini en définissant is_default_profile
sur true
, et deux profils avec des noms qui coïncident avec ceux des sujets précédemment définis : sync_topic
et un autre pour async_topic
. Ces deux derniers profils définissent le mode de publication sur SYNCHRONOUS
ou ASYNCHRONOUS
selon le cas. Notez également que tous les profils spécifient une valeur historyMemoryPolicy
, qui est nécessaire pour que l’exemple fonctionne, et la raison sera expliquée plus tard dans ce tutoriel.
Exécuter le nœud de l’éditeur
Vous devrez exporter les variables d’environnement suivantes pour que le XML soit chargé :
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
SET RMW_IMPLEMENTATION=rmw_fastrtps_cpp
SET RMW_FASTRTPS_USE_QOS_FROM_XML=1
SET FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
Enfin, assurez-vous d’avoir sourcé vos fichiers de configuration et exécutez le nœud :
source install/setup.bash
ros2 run sync_async_node_example_cpp SyncAsyncWriter
Vous devriez voir les éditeurs envoyer les données depuis le nœud de publication, comme suit :
[INFO] [1612972049.994630332] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 0'
[INFO] [1612972049.995097767] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 0'
[INFO] [1612972050.494478706] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 1'
[INFO] [1612972050.494664334] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 1'
[INFO] [1612972050.994368474] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 2'
[INFO] [1612972050.994549851] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 2'
Vous avez maintenant un éditeur synchrone et un éditeur asynchrone exécutés dans le même nœud.
Créer un nœud avec les abonnés
Ensuite, un nouveau nœud avec les abonnés qui écouteront les publications sync_topic
et async_topic
va être créé. Dans un nouveau fichier source nommé src/sync_async_reader.cpp
écrivez le contenu suivant :
#include <functional>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class SyncAsyncSubscriber : public rclcpp::Node
{
public:
SyncAsyncSubscriber()
: Node("sync_async_subscriber")
{
// Create the synchronous subscriber on topic 'sync_topic'
// and tie it to the topic_callback
sync_subscription_ = this->create_subscription<std_msgs::msg::String>(
"sync_topic", 10, std::bind(&SyncAsyncSubscriber::topic_callback, this, _1));
// Create the asynchronous subscriber on topic 'async_topic'
// and tie it to the topic_callback
async_subscription_ = this->create_subscription<std_msgs::msg::String>(
"async_topic", 10, std::bind(&SyncAsyncSubscriber::topic_callback, this, _1));
}
private:
/**
* Actions to run every time a new message is received
*/
void topic_callback(const std_msgs::msg::String & msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
}
// A subscriber that listens to topic 'sync_topic'
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sync_subscription_;
// A subscriber that listens to topic 'async_topic'
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr async_subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SyncAsyncSubscriber>());
rclcpp::shutdown();
return 0;
}
Ouvrez le fichier CMakeLists.txt
et ajoutez un nouvel exécutable et nommez-le SyncAsyncReader
sous le précédent SyncAsyncWriter
:
add_executable(SyncAsyncReader src/sync_async_reader.cpp)
ament_target_dependencies(SyncAsyncReader rclcpp std_msgs)
install(TARGETS
SyncAsyncReader
DESTINATION lib/${PROJECT_NAME})
Ajouter les profils d’abonnés au XML
Les profils de configuration des abonnés peuvent être ajoutés dans le même fichier XML SyncAsync.xml
. Pour le moment, seul le profil par défaut sera défini. Les profils d’abonnés seront étendus ultérieurement. Ouvrez le fichier SyncAsync.xml
et ajoutez les profils suivants dans la balise <profiles>
:
<!-- subscriber profile for topic sync_topic -->
<subscriber profile_name="default_subscriber" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</subscriber>
Exécuter le nœud d’abonné
Avec le nœud d’éditeur en cours d’exécution dans un terminal, ouvrez-en un autre et exportez les variables d’environnement requises pour que le XML soit chargé :
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
SET RMW_IMPLEMENTATION=rmw_fastrtps_cpp
SET RMW_FASTRTPS_USE_QOS_FROM_XML=1
SET FASTRTPS_DEFAULT_PROFILES_FILE=path/to/SyncAsync.xml
Enfin, assurez-vous d’avoir sourcé vos fichiers de configuration et exécutez le nœud :
source install/setup.bash
ros2 run sync_async_node_example_cpp SyncAsyncReader
Vous devriez voir les abonnés recevoir les données du nœud de publication, comme suit :
[INFO] [1612972054.495429090] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 10'
[INFO] [1612972054.995410057] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 10'
[INFO] [1612972055.495453494] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 11'
[INFO] [1612972055.995396561] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 11'
[INFO] [1612972056.495534818] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 12'
[INFO] [1612972056.995473953] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 12'
Analyse de l’exemple
Profils de configuration XML
Le fichier XML définit plusieurs configurations pour les éditeurs et les abonnés. Vous pouvez avoir un profil de configuration d’éditeur par défaut et plusieurs profils d’éditeur spécifiques à un sujet. La seule exigence est que tous les profils d’éditeur aient un nom différent et qu’il n’y ait qu’un seul profil par défaut. Il en va de même pour les abonnés.
Afin de définir une configuration pour un sujet spécifique, nommez simplement le profil après le nom du sujet ROS 2 (comme /sync_topic
et /async_topic
dans l’exemple), et rmw_fastrtps
s’appliquera ce profil à tous les éditeurs et abonnés pour ce sujet. Le profil de configuration par défaut est identifié par l’attribut is_default_profile
défini sur true
, et agit comme un profil de secours lorsqu’il n’y en a pas d’autre avec un nom correspondant au nom du sujet.
La variable d’environnement FASTRTPS_DEFAULT_PROFILES_FILE
est utilisée pour informer Fast DDS du chemin vers le fichier XML avec les profils de configuration à charger.
RMW_FASTRTPS_USE_QOS_FROM_XML
Parmi tous les attributs configurables, rmw_fastrtps
traite publishMode
et historyMemoryPolicy
différemment. Par défaut, ces valeurs sont définies sur ASYNCHRONOUS
et PREALLOCATED_WITH_REALLOC
dans l’implémentation rmw_fastrtps
, et les valeurs définies sur le fichier XML sont ignorées. Afin d’utiliser les valeurs du fichier XML, la variable d’environnement RMW_FASTRTPS_USE_QOS_FROM_XML
doit être définie sur 1
.
Cependant, cela implique une autre mise en garde : si RMW_FASTRTPS_USE_QOS_FROM_XML
est défini, mais que le fichier XML ne définit pas publishMode
ou historyMemoryPolicy
, ces attributs prennent la valeur par défaut Fast DDS à la place de la valeur par défaut de rmw_fastrtps
. Ceci est important, en particulier pour historyMemoryPolicy
, car la valeur par défaut Fast DDS est PREALLOCATED
qui ne fonctionne pas avec les types de données de sujet ROS2. Par conséquent, dans l’exemple, une valeur valide pour cette stratégie a été explicitement définie (DYNAMIC
).
Priorisation de rmw_qos_profile_t
La qualité de service ROS 2 contenue dans rmw_qos_profile_t est toujours respectée, sauf si elle est définie sur *_SYSTEM_DEFAULT
. Dans ce cas, les valeurs XML (ou les valeurs par défaut Fast DDS en l’absence de XML) sont appliquées. Cela signifie que si une QoS dans rmw_qos_profile_t
est définie sur autre chose que *_SYSTEM_DEFAULT
, la valeur correspondante dans le XML est ignorée.
Utilisation d’autres fonctionnalités FastDDS avec XML
Bien que nous ayons créé un nœud avec deux éditeurs avec une configuration différente, il n’est pas facile de vérifier qu’ils se comportent différemment. Maintenant que les bases des profils XML ont été couvertes, utilisons-les pour configurer quelque chose qui a un effet visuel sur les nœuds. Plus précisément, un nombre maximal d’abonnés correspondants sur l’un des éditeurs et une définition de partition sur l’autre seront définis. Notez que ce ne sont que des exemples très simples parmi tous les attributs de configuration qui peuvent être réglés sur rmw_fastrtps
via des fichiers XML. Veuillez vous référer à *Fast DDS* documentation pour voir la liste complète des attributs qui peut être configuré via des fichiers XML.
Limiter le nombre d’abonnés correspondants
Ajoutez un nombre maximum d’abonnés correspondants au profil d’éditeur /async_topic
. Ça devrait ressembler à ça:
<!-- publisher profile for topic async_topic -->
<publisher profile_name="/async_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>ASYNCHRONOUS</kind>
</publishMode>
</qos>
<matchedSubscribersAllocation>
<initial>0</initial>
<maximum>1</maximum>
<increment>1</increment>
</matchedSubscribersAllocation>
</publisher>
Le nombre d’abonnés correspondants est limité à un.
Ouvrez maintenant trois terminaux et n’oubliez pas de sourcer les fichiers d’installation et de définir les variables d’environnement requises. Sur le premier terminal, exécutez le nœud éditeur et le nœud abonné sur les deux autres. Vous devriez voir que seul le premier nœud d’abonné reçoit les messages des deux sujets. Le second n’a pas pu terminer le processus de correspondance dans le /async_topic
car l’éditeur l’en a empêché, car il avait déjà atteint son maximum d’éditeurs correspondants. Par conséquent, seuls les messages du /sync_topic
vont être reçus dans ce troisième terminal :
[INFO] [1613127657.088860890] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 18'
[INFO] [1613127657.588896594] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 19'
[INFO] [1613127658.088849401] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 20'
Utilisation de partitions dans le sujet
La fonction de partitions peut être utilisée pour contrôler quels éditeurs et abonnés échangent des informations dans le même sujet.
Les partitions introduisent un concept de niveau d’isolation d’entité logique à l’intérieur de l’isolation physique induite par un ID de domaine. Pour qu’un éditeur puisse communiquer avec un abonné, il doit appartenir au moins à une partition commune. Les partitions représentent un autre niveau pour séparer les éditeurs et les abonnés au-delà du domaine et du sujet. Contrairement au domaine et au sujet, un point de terminaison peut appartenir à plusieurs partitions en même temps. Pour que certaines données soient partagées sur différents domaines ou sujets, il doit y avoir un éditeur différent pour chacun, partageant son propre historique des modifications. Cependant, un seul éditeur peut partager le même échantillon de données sur différentes partitions à l’aide d’un seul changement de données de sujet, réduisant ainsi la surcharge du réseau.
Changeons l’éditeur /sync_topic
pour partitionner part1
et créons un nouvel abonné /sync_topic
qui utilise la partition part2
. Leurs profils devraient maintenant ressembler à ceci :
<!-- publisher profile for topic sync_topic -->
<publisher profile_name="/sync_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>SYNCHRONOUS</kind>
</publishMode>
<partition>
<names>
<name>part1</name>
</names>
</partition>
</qos>
</publisher>
<!-- subscriber profile for topic sync_topic -->
<subscriber profile_name="/sync_topic">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<partition>
<names>
<name>part2</name>
</names>
</partition>
</qos>
</subscriber>
Assurez-vous également de supprimer le profil d’abonné « default_subscriber ». Ouvrez deux terminaux. N’oubliez pas de sourcer les fichiers d’installation et de définir les variables d’environnement requises. Sur le premier terminal, exécutez le nœud éditeur et le nœud abonné sur l’autre. Vous devriez voir que seuls les messages /async_topic
parviennent à l’abonné. L’abonné /sync_topic
ne reçoit pas les données car elles se trouvent dans une partition différente de l’éditeur correspondant.
[INFO] [1612972054.995410057] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 10'
[INFO] [1612972055.995396561] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 11'
[INFO] [1612972056.995473953] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 12'
Configurer un service et un client
Les services et les clients ont chacun un éditeur et un abonné, qui communiquent via deux sujets différents. Par exemple, pour un service nommé ping
il y a :
Un abonné au service écoutant les requêtes sur
/rq/ping
.Un éditeur de service envoyant des réponses sur
/rr/ping
.Un éditeur client envoyant des requêtes sur
/rq/ping
.Un abonné client écoutant les réponses sur
/rr/ping
.
Bien que vous puissiez utiliser ces noms de rubrique pour définir les profils de configuration sur le XML, vous pouvez parfois souhaiter appliquer le même profil à tous les services ou clients sur un nœud. Au lieu de copier le même profil avec tous les noms de sujets générés pour tous les services, vous pouvez simplement créer une paire de profils d’éditeur et d’abonné nommée service
. La même chose peut être faite pour les clients créant une paire nommée client
.
Créer les nœuds avec le service et le client
Commencez à créer le nœud avec le service. Ajoutez un nouveau fichier source nommé src/ping_service.cpp
sur votre package avec le contenu suivant :
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/trigger.hpp"
/**
* Service action: responds with success=true and prints the request on the console
*/
void ping(const std::shared_ptr<example_interfaces::srv::Trigger::Request> request,
std::shared_ptr<example_interfaces::srv::Trigger::Response> response)
{
// The request data is unused
(void) request;
// Build the response
response->success = true;
// Log to the console
RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Incoming request");
RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Sending back response");
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
// Create the node and the service
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("ping_server");
rclcpp::Service<example_interfaces::srv::Trigger>::SharedPtr service =
node->create_service<example_interfaces::srv::Trigger>("ping", &ping);
// Log that the service is ready
RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Ready to serve.");
// run the node
rclcpp::spin(node);
rclcpp::shutdown();
}
Créez le client dans un fichier nommé src/ping_client.cpp
avec le contenu suivant :
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/trigger.hpp"
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
// Create the node and the client
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("ping_client");
rclcpp::Client<example_interfaces::srv::Trigger>::SharedPtr client =
node->create_client<example_interfaces::srv::Trigger>("ping");
// Create a request
auto request = std::make_shared<example_interfaces::srv::Trigger::Request>();
// Wait for the service to be available
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("ping_client"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Service not available, waiting again...");
}
// Now that the service is available, send the request
RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Sending request");
auto result = client->async_send_request(request);
// Wait for the result and log it to the console
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Response received");
} else {
RCLCPP_ERROR(rclcpp::get_logger("ping_client"), "Failed to call service ping");
}
rclcpp::shutdown();
return 0;
}
Ouvrez le fichier CMakeLists.txt
et ajoutez deux nouveaux exécutables ping_service
et ping_client
:
find_package(example_interfaces REQUIRED)
add_executable(ping_service src/ping_service.cpp)
ament_target_dependencies(ping_service example_interfaces rclcpp)
add_executable(ping_client src/ping_client.cpp)
ament_target_dependencies(ping_client example_interfaces rclcpp)
install(TARGETS
ping_service
DESTINATION lib/${PROJECT_NAME})
install(TARGETS
ping_client
DESTINATION lib/${PROJECT_NAME})
Enfin, construisez le package.
Créer les profils XML pour le service et le client
Créez un fichier avec le nom ping.xml
avec le contenu suivant :
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<!-- default publisher profile -->
<publisher profile_name="default_publisher" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</publisher>
<!-- default subscriber profile -->
<subscriber profile_name="default_subscriber" is_default_profile="true">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
</subscriber>
<!-- service publisher is SYNC -->
<publisher profile_name="service">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>SYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
<!-- client publisher is ASYNC -->
<publisher profile_name="client">
<historyMemoryPolicy>DYNAMIC</historyMemoryPolicy>
<qos>
<publishMode>
<kind>ASYNCHRONOUS</kind>
</publishMode>
</qos>
</publisher>
</profiles>
Ce fichier de configuration définit le mode de publication sur SYNCHRONOUS
sur le service et sur ASYNCHRONOUS
sur le client. Notez que nous ne définissons que les profils d’éditeur pour le service et le client, mais des profils d’abonnés peuvent également être fournis.
Exécuter les nœuds
Ouvrez deux terminaux et sourcez les fichiers de configuration sur chacun d’eux. Définissez ensuite les variables d’environnement requises pour le XML à charger :
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/ping.xml
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=path/to/ping.xml
SET RMW_IMPLEMENTATION=rmw_fastrtps_cpp
SET RMW_FASTRTPS_USE_QOS_FROM_XML=1
SET FASTRTPS_DEFAULT_PROFILES_FILE=path/to/ping.xml
Sur le premier terminal, exécutez le nœud de service.
ros2 run sync_async_node_example_cpp ping_service
Vous devriez voir le service en attente de demandes :
[INFO] [1612977403.805799037] [ping_server]: Ready to serve
Sur le deuxième terminal, exécutez le nœud client.
ros2 run sync_async_node_example_cpp ping_client
Vous devriez voir le client envoyer la requête et recevoir la réponse :
[INFO] [1612977404.805799037] [ping_client]: Sending request
[INFO] [1612977404.825473835] [ping_client]: Response received
Dans le même temps, la sortie dans la console du serveur a été mise à jour :
[INFO] [1612977403.805799037] [ping_server]: Ready to serve
[INFO] [1612977404.807314904] [ping_server]: Incoming request
[INFO] [1612977404.836405125] [ping_server]: Sending back response