À propos des interfaces ROS 2 internes

Les interfaces ROS internes sont des C publics APIs destinés à être utilisés par les développeurs qui créent des |bibliothèques clientes| ou l’ajout d’un nouveau middleware sous-jacent, mais ne sont pas destinés à être utilisés par les utilisateurs ROS typiques. Le ROS |bibliothèques clientes| fournir à l’utilisateur face aux APIs que la plupart des utilisateurs de ROS connaissent et peuvent être disponibles dans une variété de langages de programmation.

Présentation de l’architecture de l’API interne

Il existe deux interfaces internes principales :

  • l’interface du middleware ROS (rmw API)

  • l’interface de la bibliothèque cliente ROS (rcl API)

L”API rmw est l’interface entre la pile logicielle ROS 2 et l’implémentation middleware sous-jacente. Le middleware sous-jacent utilisé pour ROS 2 est une implémentation DDS ou RTPS et est responsable des mécanismes de découverte, de publication et d’abonnement, des mécanismes de demande-réponse pour les services et de la sérialisation des types de messages.

L”API rcl est un niveau légèrement supérieur API qui est utilisé pour implémenter les |bibliothèques clientes| et ne touche pas directement l’implémentation du middleware, mais le fait plutôt via l’abstraction de l’interface du middleware ROS (rmw API).

pile logicielle ros2

Comme le montre le diagramme, ces APIs sont empilés de telle sorte que l’utilisateur ROS typique utilisera la |bibliothèque client| API, par ex. rclcpp, pour implémenter leur code (exécutable ou bibliothèque). L’implémentation des |bibliothèques clientes|, par ex. rclcpp, utilisez l’interface rcl qui donne accès au graphe ROS et aux événements de graphe. L’implémentation rcl utilise à son tour l’API| rmw | pour accéder au graphique ROS. Le but de l’implémentation rcl est de fournir une implémentation commune pour des concepts et des utilitaires ROS plus complexes qui peuvent être utilisés par diverses |bibliothèques clientes|, tout en restant indépendant du middleware sous-jacent utilisé. Le but de l’interface rmw est de capturer la fonctionnalité minimale absolue du middleware nécessaire pour prendre en charge les bibliothèques clientes de ROS. Enfin, l’implémentation de l”API rmw est fourni par un package spécifique à l’implémentation du middleware, par ex. rmw_fastrtps_cpp, dont la bibliothèque est compilée avec les interfaces et les types DDS spécifiques au fournisseur.

Dans le diagramme ci-dessus, il y a aussi une boîte intitulée ros_to_dds, et le but de cette boîte est de représenter une catégorie de packages possibles qui permettent à l’utilisateur d’accéder aux objets et paramètres spécifiques au fournisseur DDS en utilisant les équivalents ROS. L’un des objectifs de cette interface d’abstraction est d’isoler complètement le code de l’espace utilisateur ROS du middleware utilisé, de sorte que le changement de fournisseur DDS ou même de technologie middleware ait un impact minimal sur le code des utilisateurs. Cependant, nous reconnaissons qu’il est parfois utile d’accéder à la mise en œuvre et d’ajuster manuellement les paramètres malgré les conséquences que cela pourrait avoir. En exigeant l’utilisation de l’un de ces packages pour accéder aux objets du fournisseur DDS sous-jacent, nous pouvons éviter d’exposer des symboles et des en-têtes spécifiques au fournisseur dans l’interface normale. Cela permet également de voir facilement quel code viole potentiellement la portabilité du fournisseur en inspectant les dépendances du paquet pour voir si l’un de ces paquets ros_to_dds est utilisé.

Interfaces spécifiques au type

Tout au long du chemin, il y a certaines parties des APIs qui sont nécessairement spécifiques aux types de messages échangés, par ex. publier un message ou s’abonner à un sujet, et nécessitent donc un code généré pour chaque type de message. Le diagramme suivant présente le chemin à partir des fichiers rosidl définis par l’utilisateur, par ex. Fichiers .msg, au code spécifique au type utilisé par l’utilisateur et le système pour exécuter des fonctions spécifiques au type :

pile de support de type statique ros2 idl

Figure : organigramme de la génération de support de type « statique », des fichiers rosidl au code utilisateur.

Le côté droit du diagramme montre comment les fichiers .msg sont transmis directement aux générateurs de code spécifiques à la langue, par ex. rosidl_generator_cpp ou rosidl_generator_py. Ces générateurs sont chargés de créer le code que l’utilisateur inclura (ou importera) et utilisera comme représentation en mémoire des messages qui ont été définis dans les fichiers .msg. Par exemple, considérez le message std_msgs/String, un utilisateur peut utiliser ce fichier en C++ avec l’instruction #include <std_msgs/msg/string.hpp>, ou il peut utiliser l’instruction from std_msgs.msg import String en Python. Ces instructions fonctionnent grâce aux fichiers générés par ces packages générateurs spécifiques au langage (mais indépendants du middleware).

Séparément, les fichiers .msg sont utilisés pour générer le code de support de type pour chaque type. Dans ce contexte, le support de type signifie : des métadonnées ou des fonctions qui sont spécifiques à un type donné et qui sont utilisées par le système pour effectuer des tâches particulières pour le type donné. La prise en charge du type pour un message donné peut inclure des éléments tels qu’une liste des noms et des types pour chaque champ du message. Il peut également contenir une référence au code qui peut effectuer des tâches particulières pour ce type, par ex. publier un message.

Prise en charge du type statique

Lorsque la prise en charge du type fait référence à du code pour effectuer des fonctions particulières pour un type de message spécifique, ce code doit parfois effectuer un travail spécifique au middleware. Par exemple, considérez la fonction de publication spécifique au type, lors de l’utilisation du « fournisseur A », la fonction devra appeler certaines des API du « fournisseur A », mais lors de l’utilisation du « fournisseur B », elle devra appeler le « fournisseur B » l”API. Pour autoriser le code spécifique au fournisseur du middleware, les fichiers .msg définis par l’utilisateur peuvent entraîner la génération d’un code spécifique au fournisseur. Ce code spécifique au fournisseur est toujours caché à l’utilisateur via l’abstraction de prise en charge de type, qui est similaire au fonctionnement du modèle « Private Implementation » (ou Pimpl).

Prise en charge du type statique avec DDS

Pour les fournisseurs de middleware basés sur DDS, et plus particulièrement ceux qui génèrent du code basé sur les fichiers OMG IDL (fichiers .idl), les fichiers rosidl définis par l’utilisateur (fichiers .msg) sont convertis en fichiers OMG IDL équivalents (fichiers .idl). À partir de ces fichiers OMG IDL, un code spécifique au fournisseur est créé puis utilisé dans les fonctions spécifiques au type qui sont référencées par la prise en charge du type pour un type donné. Le diagramme ci-dessus le montre sur le côté gauche, où les fichiers .msg sont consommés par le package rosidl_dds pour produire des fichiers .idl, puis ces fichiers .idl sont donnés aux packages de génération de prise en charge spécifiques à la langue et au fournisseur DDS.

Par exemple, considérez l’implémentation Fast DDS, qui a un package appelé rosidl_typesupport_fastrtps_cpp. Ce package est chargé de générer du code pour gérer des choses comme la conversion d’un objet de message C++ en un tampon d’octets sérialisé à écrire sur le réseau. Ce code, bien que spécifique à Fast DDS, n’est toujours pas exposé à l’utilisateur en raison de l’abstraction dans le code de prise en charge du type.

Prise en charge des types dynamiques

Une autre façon d’implémenter la prise en charge des types est d’avoir des fonctions génériques pour des choses comme la publication dans un sujet, plutôt que de générer une version de la fonction pour chaque type de message. Pour ce faire, cette fonction générique a besoin de certaines méta-informations sur le type de message publié, comme une liste de noms de champs et de types dans l’ordre dans lequel ils apparaissent dans le type de message. Ensuite, pour publier un message, vous appelez une fonction de publication générique et transmettez un message à publier avec une structure qui contient les métadonnées nécessaires sur le type de message. C’est ce qu’on appelle la prise en charge de type « dynamique », par opposition à la prise en charge de type « statique » qui nécessite des versions générées d’une fonction pour chaque type.

pile de support de type dynamique ros2 idl

Figure : organigramme de la génération de support de type « dynamique », des fichiers rosidl au code utilisateur.

Le diagramme ci-dessus montre le flux depuis les fichiers rosidl définis par l’utilisateur jusqu’au code utilisateur généré. Il est très similaire au diagramme de prise en charge de type statique et ne diffère que par la manière dont la prise en charge de type est générée, représentée par le côté gauche du diagramme. Dans le support de type dynamique, les fichiers .msg sont convertis directement en code utilisateur.

Ce code est également indépendant du middleware, car il ne contient que des méta-informations sur les messages. La fonction pour effectuer réellement le travail, par ex. la publication dans un sujet, est générique pour le type de message et effectuera tous les appels nécessaires aux API spécifiques au middleware. Notez que plutôt que des packages spécifiques au fournisseur dds fournissant le code de prise en charge de type, ce qui est le cas dans la prise en charge de type statique, cette méthode a un package indépendant du middleware pour chaque langue, par ex. rosidl_typesupport_introspection_c et rosidl_typesupport_introspection_cpp. La partie introspection du nom du package fait référence à la possibilité d’introspecter n’importe quelle instance de message avec les métadonnées générées pour le type de message. Il s’agit de la capacité fondamentale qui permet des implémentations génériques de fonctions telles que « publier dans un sujet ».

Cette approche présente l’avantage que tout le code généré est indépendant du middleware, ce qui signifie qu’il peut être réutilisé pour différentes implémentations de middleware, tant qu’elles permettent la prise en charge de type dynamique. Il en résulte également moins de code généré, ce qui réduit le temps de compilation et la taille du code.

Cependant, la prise en charge de type dynamique nécessite que le middleware sous-jacent prenne en charge une forme similaire de prise en charge de type dynamique. Dans le cas de DDS, la norme DDS-XTypes permet la publication de messages à l’aide de méta-informations plutôt que de code généré. DDS-XTypes, ou quelque chose de similaire, est requis dans le middleware sous-jacent afin de prendre en charge la prise en charge de type dynamique. De plus, cette approche de la prise en charge des types est normalement plus lente que l’alternative de prise en charge des types statiques. Le code généré spécifique au type dans la prise en charge du type statique peut être écrit pour être plus efficace car il n’a pas besoin d’itérer sur les métadonnées du type de message pour effectuer des opérations telles que la sérialisation.

Le référentiel rcl

L’interface de la bibliothèque cliente ROS (rcl API) peut être utilisée par les |bibliothèques clientes| (par exemple rclc, rclcpp, rclpy, etc.) afin d’éviter la duplication de la logique et des fonctionnalités. En réutilisant l”API rcl, les bibliothèques clientes peuvent être plus petites et plus cohérentes les unes avec les autres. Certaines parties de la bibliothèque cliente sont intentionnellement laissées de côté par rcl API parce que la méthode idiomatique du langage doit être utilisée pour mettre en œuvre ces parties du système. Un bon exemple de ceci est le modèle d’exécution, que rcl ne traite pas du tout. Au lieu de cela, la bibliothèque cliente devrait fournir une solution idiomatique de langage comme pthreads en C, std::thread en C++11 et threading.Thread en Python. Généralement, l’interface rcl fournit des fonctions qui ne sont pas spécifiques à un modèle de langage et ne sont pas spécifiques à un type de message particulier.

L”API rcl est situé dans le référentiel ros2/rcl sur GitHub et contient l’interface sous forme d’en-têtes C. L’implémentation rcl C est fournie par le rcl package dans le même référentiel. Cette implémentation évite le contact direct avec le middleware en utilisant à la place les APIs rmw et rosidl.

Pour une définition complète de rcl API, consultez sa documentation API :

Le référentiel rmw

L’interface middleware ROS (rmw API) est l’ensemble minimal de fonctionnalités middleware primitives nécessaires pour construire ROS par dessus. Les fournisseurs de différentes implémentations de middleware doivent implémenter cette interface afin de prendre en charge l’ensemble de la pile ROS. Actuellement, toutes les implémentations de middleware sont destinées à différents fournisseurs de DDS.

L”API rmw se trouve dans le dépôt ros2/rmw. Le |paquet| rmw contient les en-têtes C qui définissent l’interface dont l’implémentation est assurée par les différents packages d’implémentations rmw pour différents fournisseurs de DDS.

Pour une définition de l”API rmw, voir l”API documents :

api/rmw/index.html

Le référentiel rosidl

L”API rosidl se compose de quelques fonctions et types statiques liés aux messages, ainsi que d’une définition du code qui doit être généré par les messages dans différentes langues. Le code de message généré spécifié dans l”API sera spécifique à la langue, mais peut ou non réutiliser le code généré pour d’autres langues. Le code de message généré spécifié dans l”API contient des éléments tels que la structure des données du message, les fonctions de construction, de destruction, etc. L”API implémentera également un moyen d’obtenir la structure de support de type pour le type de message, qui est utilisée lors de la publication ou de l’abonnement à un sujet de ce type de message.

Il existe plusieurs dépôts qui jouent un rôle dans le rosidl API et la mise en œuvre.

Le référentiel rosidl, situé sur GitHub à ros2/rosidl, définit la syntaxe IDL du message, c’est-à-dire la syntaxe de .msg fichiers, fichiers .srv, etc., et contient des packages pour analyser les fichiers, pour fournir une infrastructure CMake pour générer du code à partir des messages, pour générer du code indépendant de l’implémentation (en-têtes et fichiers source) et pour établir l’ensemble de générateurs par défaut. Le dépôt contient ces packages :

  • rosidl_cmake : fournit des fonctions CMake et des modules CMake pour générer du code à partir de fichiers rosidl, par ex. Fichiers .msg, fichiers .srv, etc.

  • rosidl_default_generators : définit la liste des générateurs par défaut qui garantit qu’ils sont installés en tant que dépendances, mais d’autres générateurs injectés peuvent également être utilisés.

  • rosidl_generator_c : fournit des outils pour générer des fichiers d’en-tête C (.h) pour les fichiers rosidl.

  • rosidl_generator_cpp : fournit des outils pour générer des fichiers d’en-tête C++ (.hpp) pour les fichiers rosidl.

  • rosidl_generator_py : fournit des outils pour générer des modules Python pour les fichiers rosidl.

  • rosidl_parser : fournit Python API pour analyser les fichiers rosidl.

Générateurs pour d’autres langues, par ex. rosidl_generator_java, sont hébergés en externe (dans différents référentiels) mais utiliseraient le même mécanisme que les générateurs ci-dessus utilisent pour « s’enregistrer » en tant que générateur rosidl.

En plus des |forfaits| mentionnés ci-dessus pour analyser et générer des en-têtes pour les fichiers rosidl, le référentiel rosidl contient également des packages concerné par le « support de type » pour les types de messages définis dans les fichiers. La prise en charge des types fait référence à la capacité d’interpréter et de manipuler les informations représentées par des instances de messages ROS de types particuliers (publier les messages, par exemple). La prise en charge des types peut être fournie par le code généré au moment de la compilation ou par programmation en fonction du contenu du fichier rosidl, par ex. le fichier .msg ou .srv, et les données reçues, en introspectant les données. Dans ce dernier cas, où la prise en charge du type se fait par l’interprétation des messages à l’exécution, le code de message généré par ROS 2 peut être indépendant de l’implémentation rmw. Les packages qui fournissent ce type de support via l’introspection des données sont :

  • rosidl_typesupport_introspection_c : fournit des outils pour générer du code C pour prendre en charge les types de données de message rosidl.

  • rosidl_typesupport_introspection_cpp : fournit des outils pour générer du code C++ pour prendre en charge les types de données de message rosidl.

Dans le cas où le support de type doit être généré au moment de la compilation au lieu d’être généré par programme, un package spécifique à l’implémentation rmw devra être utilisé. En effet, une implémentation rmw particulière nécessitera généralement que les données soient stockées et manipulées d’une manière spécifique au fournisseur DDS afin que l’implémentation DDS puisse les utiliser. Voir la section Interfaces spécifiques au type ci-dessus pour plus de détails.

Pour plus d’informations sur le contenu exact de l”API rosidl (statique et généré) voir cette page :

Avertissement

TODO : lien vers la définition de rosidl APIs

Le référentiel rutils

ROS 2 C Utilities (rcutils) est un C API composé de macros, de fonctions et de structures de données utilisées dans la base de code ROS 2. Ceux-ci sont principalement utilisés pour la gestion des erreurs, l’analyse des arguments de ligne de commande et la journalisation qui ne sont pas spécifiques aux couches client ou middleware et peuvent être partagées par les deux.

Les rutils API et l’implémentation se trouvent dans le référentiel ros2/rcutils sur GitHub qui contient l’interface sous forme d’en-têtes C.

Pour une définition complète de rcutils API, consultez sa documentation API