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
Contenu
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
colcon build --packages-select tutorial_interfaces
colcon build --merge-install --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
. install/setup.bash
call install/setup.bat
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;
}
import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(Num, 'topic', 10) # CHANGE
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = Num() # CHANGE
msg.num = self.i # CHANGE
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%d"' % msg.num) # CHANGE
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
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;
}
import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
Num, # CHANGE
'topic',
self.listener_callback,
10)
self.subscription
def listener_callback(self, msg):
self.get_logger().info('I heard: "%d"' % msg.num) # CHANGE
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
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>
<exec_depend>tutorial_interfaces</exec_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
colcon build --packages-select py_pubsub
Sous Windows :
colcon build --merge-install --packages-select py_pubsub
Ouvrez ensuite deux nouveaux terminaux, source ros2_ws
dans chacun, et exécutez :
ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener
ros2 run py_pubsub talker
ros2 run py_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();
}
from tutorial_interfaces.srv import AddThreeInts # CHANGE
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddThreeInts, 'add_three_ints', self.add_three_ints_callback) # CHANGE
def add_three_ints_callback(self, request, response):
response.sum = request.a + request.b + request.c # CHANGE
self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c)) # CHANGE
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
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;
}
from tutorial_interfaces.srv import AddThreeInts # CHANGE
import sys
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddThreeInts, 'add_three_ints') # CHANGE
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddThreeInts.Request() # CHANGE
def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
self.req.c = int(sys.argv[3]) # CHANGE
self.future = self.cli.call_async(self.req)
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
minimal_client.send_request()
while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_three_ints: for %d + %d + %d = %d' % # CHANGE
(minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum)) # CHANGE
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
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>
<exec_depend>tutorial_interfaces</exec_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
colcon build --packages-select py_srvcli
Sous Windows :
colcon build --merge-install --packages-select py_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
ros2 run py_srvcli service
ros2 run py_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.