TP 3 — Services et paramètres
Objectifs
offrir un service : une requête, une réponse ;
régler les limites de la bibliothèque micro-ROS, et la reconstruire ;
exposer des paramètres, modifiables depuis ROS 2 sans recompiler, et les valider.
Durée indicative : une séance de 3 h. Prérequis : TP 2 — Recevoir des ordres, dont on reprend le projet.
Ce qu’il faut savoir
Topic, service ou paramètre ?
Mécanisme |
Principe |
Exemple |
|---|---|---|
topic |
flux de messages, sans réponse, à n’importe quel nombre d’abonnés |
une mesure, une consigne de vitesse |
service |
une requête, une réponse, à un seul serveur |
remettre l’odométrie à zéro, déclencher un bip |
paramètre |
une valeur nommée, lue et modifiée par |
un réglage : un rayon de roue, un seuil |
Un service est fait pour une action ponctuelle, dont on veut savoir si elle a réussi. Un paramètre, pour un réglage, qu’on change rarement.
Le robot en offre de chaque sorte : base/reset_odom est un service ; les
rayons de roue et l’entraxe sont des paramètres de sa carte.
Les limites de micro-ROS
micro-ROS n’alloue pas de mémoire au fil de l’eau : le nombre de nœuds,
d’éditeurs, d’abonnés, de services est fixé à la compilation de la
bibliothèque, dans le fichier colcon.meta. Par défaut :
RMW_UXRCE_MAX_PUBLISHERS = 10
RMW_UXRCE_MAX_SUBSCRIPTIONS = 5
RMW_UXRCE_MAX_SERVICES = 1
RMW_UXRCE_MAX_CLIENTS = 1
Au-delà, la création échoue — et rien d’autre ne l’indique que le code de retour.
Étape 1 — Un service : le buzzer
Configurez PC13 en sortie (le buzzer actif, TP 3 de la partie 1).
Créez un service
buzzerde typestd_srvs/srv/SetBool:data: truemet le buzzer en marche,falsel’arrête. La réponse indiquesuccesset un courtmessage.rclc_service_init_default(&srv_buzzer, &node, ROSIDL_GET_SRV_TYPE_SUPPORT(std_srvs, srv, SetBool), "buzzer"); rclc_executor_add_service(&executor, &srv_buzzer, &req_buzzer, &res_buzzer, buzzer_cb);
Le champ
messagede la réponse est une chaîne : comme au TP 2, c’est à vous de fournir sa mémoire.Un élément de plus pour l’exécuteur.
Depuis le PC :
ros2 service list ros2 service call /tp/buzzer std_srvs/srv/SetBool "{data: true}"
Questions
Que renvoie l’appel ? Que se passe-t-il si la carte est débranchée ?
Pourquoi un service plutôt qu’un topic
std_msgs/msg/Bool?
Solution de l’étape 1
#include <std_srvs/srv/set_bool.h>
static rcl_service_t srv_buzzer;
static std_srvs__srv__SetBool_Request req_buzzer; /* static : vivent */
static std_srvs__srv__SetBool_Response res_buzzer; /* avec le service */
static char res_buzzer_text[32];
/* Copie un texte dans une chaîne micro-ROS dont la mémoire est à nous. */
static void set_text(rosidl_runtime_c__String *s, char *buf, size_t cap, const char *txt)
{
strncpy(buf, txt, cap - 1u);
buf[cap - 1u] = '\0';
s->data = buf;
s->size = strlen(buf);
s->capacity = cap;
}
static void buzzer_cb(const void *req_in, void *res_out)
{
const std_srvs__srv__SetBool_Request *req = (const std_srvs__srv__SetBool_Request *)req_in;
std_srvs__srv__SetBool_Response *res = (std_srvs__srv__SetBool_Response *)res_out;
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, req->data ? GPIO_PIN_SET : GPIO_PIN_RESET);
res->success = true;
set_text(&res->message, res_buzzer_text, sizeof res_buzzer_text,
req->data ? "buzzer on" : "buzzer off");
}
et après la création du nœud :
rclc_service_init_default(&srv_buzzer, &node,
ROSIDL_GET_SRV_TYPE_SUPPORT(std_srvs, srv, SetBool), "buzzer");
rclc_executor_add_service(&executor, &srv_buzzer, &req_buzzer,
&res_buzzer, buzzer_cb);
Le rappel de service reçoit la requête (déjà décodée par l’exécuteur) et remplit la réponse ; l’exécuteur l’envoie quand le rappel se termine. Un service est donc synchrone du point de vue du client : il attend que le rappel ait fini.
strncpy et sa borne. strncpy copie au plus cap − 1 caractères ;
on ajoute le '\0' final à la main, car strncpy ne le met pas si le
texte est trop long. La capacité déclarée doit être celle du tampon, pas
celle du texte.
Réponses aux questions 1 et 2
1. La réponse. ros2 service call affiche la requête, puis la
réponse : success=True, message='buzzer on'. Si la carte est
débranchée, le service disparaît du graphe (l’agent le retire quand la
session meurt, ou il n’existe plus après un redémarrage de l’agent), et
l’appel affiche « waiting for service to become available… » indéfiniment.
Un programme qui appelle un service doit donc toujours prévoir un délai
d’attente.
2. Service ou topic. Avec un topic, l’émetteur publie et ne sait rien
de la suite : le message est-il arrivé ? quelqu’un l’a-t-il traité ?
a-t-il réussi ? Un service répond à ces trois questions : il y a un
destinataire, il exécute l’ordre, et il dit si ça a marché (success) et
pourquoi (message). Pour une action ponctuelle qu’on veut confirmer,
c’est le bon outil. Pour un flux continu (une consigne de vitesse, une
mesure), le topic reste le bon choix : une réponse à chaque message serait
inutile et coûteuse.
Étape 2 — Un second service, et la limite
Ajoutez un service
reset_counterde typestd_srvs/srv/Trigger, qui remet à zéro le compteur du TP 1 (réintégrez-le s’il a disparu).Vérifiez le code de retour de sa création : que se passe-t-il ?
Ouvrez
micro_ros_stm32cubemx_utils/microros_static_library_ide/ library_generation/colcon.meta, et portezRMW_UXRCE_MAX_SERVICESà 8.Supprimez le dossier
micro_ros_stm32cubemx_utils/microros_static_library_ide/libmicroros, pour forcer la reconstruction de la bibliothèque, et recompilez.
Question
Pourquoi ces limites sont-elles fixées à la compilation, et pas choisies par le programme ?
Solution de l’étape 2
Le second service, sur le même modèle :
#include <std_srvs/srv/trigger.h>
static void reset_cb(const void *req_in, void *res_out)
{
std_srvs__srv__Trigger_Response *res = (std_srvs__srv__Trigger_Response *)res_out;
(void)req_in; /* une requête Trigger est vide */
g_counter = 0;
res->success = true;
set_text(&res->message, res_reset_text, sizeof res_reset_text, "counter reset");
}
Avec la bibliothèque par défaut, sa création échoue :
rclc_service_init_default renvoie une erreur, car
RMW_UXRCE_MAX_SERVICES vaut 1 et le service buzzer occupe déjà la
seule place. Dans colcon.meta :
"-DRMW_UXRCE_MAX_SERVICES=8",
puis on supprime microros_static_library_ide/libmicroros : l’étape de
pré-compilation voit qu’elle manque, et reconstruit la bibliothèque (une
dizaine de minutes).
Pourquoi 8 : 2 services à nous, 5 pour le serveur de paramètres de l’étape 3, et une place de marge.
Réponse à la question 3
3. Des limites fixées à la compilation. Le client micro-ROS réserve toute sa mémoire à l’avance, dans des tableaux de taille fixe : un tableau de 10 éditeurs, de 5 abonnés, de 1 service… C’est un choix délibéré pour un microcontrôleur :
pas d’allocation au fil de l’eau, donc pas de fragmentation de la mémoire au bout de plusieurs jours de fonctionnement ;
une empreinte mémoire connue à la compilation : si le programme se lie, il a la mémoire qu’il lui faut ; pas de panne de mémoire en plein fonctionnement.
Le prix : il faut dimensionner ces tableaux pour le programme, et reconstruire la bibliothèque quand on les change. Le firmware du robot l’a fait (24 éditeurs, 16 abonnés, 16 services).
Étape 3 — Des paramètres
Trois réglages de la carte deviennent des paramètres :
Nom |
Type |
Rôle |
|---|---|---|
|
entier |
période de clignotement de LED_2, de 50 à 5 000 ms |
|
réel |
seuil d’alarme de température, en °C : LED_3 allumée au-delà |
|
booléen |
autorise ou non le service |
Créez le serveur de paramètres, avec une option pour en accepter plusieurs (par défaut : quatre au plus) :
rclc_parameter_options_t o = { .notify_changed_over_dds = true, .max_params = 4, .allow_undeclared_parameters = false, .low_mem_mode = false, }; rclc_parameter_server_init_with_option(¶ms, &node, &o); rclc_executor_add_parameter_server(&executor, ¶ms, on_param_changed);
Le serveur utilise cinq services et un éditeur : comptez-les dans les limites de l’étape 2, et ajoutez
RCLC_EXECUTOR_PARAMETER_SERVER_HANDLES(5) au nombre d’éléments de l’exécuteur.Déclarez chaque paramètre, puis donnez-lui sa valeur de départ :
rclc_add_parameter(¶ms, "blink_period_ms", RCLC_PARAMETER_INT); rclc_parameter_set_int(¶ms, "blink_period_ms", 500);
Écrivez
on_param_changed: elle reçoit l’ancienne et la nouvelle valeur, et renvoietruepour accepter la modification,falsepour la refuser. Refusez une période hors de 50–5 000 ms.Important
old_paramvautNULLà la création d’un paramètre (premierrclc_parameter_set_*) : ce n’est pas une erreur, il faut accepter. Sinon, tous les paramètres restent à zéro.Dans la boucle, lisez les paramètres (
rclc_parameter_get_int…) et appliquez-les.Depuis le PC :
ros2 param list /tp/ratbot_lab ros2 param get /tp/ratbot_lab blink_period_ms ros2 param set /tp/ratbot_lab blink_period_ms 100 ros2 param set /tp/ratbot_lab blink_period_ms 10
Questions
Que répond
ros2 param setpour la valeur 10 ? Où ce refus a-t-il été décidé ?Redémarrez la carte : que valent les paramètres ? Comment le firmware du robot garde-t-il les siens d’un démarrage à l’autre ?
Pourquoi le serveur de paramètres demande-t-il cinq services ? Lesquels (
ros2 service list) ?
Solution de l’étape 3
#include <rclc_parameter/rclc_parameter.h>
static rclc_parameter_server_t params;
/* old_param NULL : création ; new_param NULL : suppression. */
static bool on_param_changed(const Parameter *old_param, const Parameter *new_param,
void *context)
{
(void)context;
if (old_param == NULL) { return true; } /* création : on accepte */
if (new_param == NULL) { return false; } /* suppression : on refuse */
if (strcmp(new_param->name.data, "blink_period_ms") == 0)
{
int64_t v = new_param->value.integer_value;
return (v >= 50) && (v <= 5000);
}
if (strcmp(new_param->name.data, "temp_alarm") == 0)
{
double v = new_param->value.double_value;
return (v > 0.0) && (v < 100.0);
}
return true; /* buzzer_enabled : tout booléen */
}
la création (l’exécuteur compte maintenant 2 + 5 éléments) :
rclc_parameter_options_t o = {
.notify_changed_over_dds = true,
.max_params = 4,
.allow_undeclared_parameters = false,
.low_mem_mode = false,
};
rclc_parameter_server_init_with_option(¶ms, &node, &o);
rclc_executor_init(&executor, &support.context,
2u + RCLC_EXECUTOR_PARAMETER_SERVER_HANDLES, &allocator);
/* … les deux services … */
rclc_executor_add_parameter_server(&executor, ¶ms, on_param_changed);
rclc_add_parameter(¶ms, "blink_period_ms", RCLC_PARAMETER_INT);
rclc_parameter_set_int(¶ms, "blink_period_ms", 500);
rclc_add_parameter(¶ms, "temp_alarm", RCLC_PARAMETER_DOUBLE);
rclc_parameter_set_double(¶ms, "temp_alarm", 35.0);
rclc_add_parameter(¶ms, "buzzer_enabled", RCLC_PARAMETER_BOOL);
rclc_parameter_set_bool(¶ms, "buzzer_enabled", true);
et dans la boucle :
int64_t period = 500;
double alarm = 35.0;
(void)rclc_parameter_get_int(¶ms, "blink_period_ms", &period);
(void)rclc_parameter_get_double(¶ms, "temp_alarm", &alarm);
/* … LED_2 clignote à « period », LED_3 s'allume au-delà de « alarm » … */
Le service buzzer consulte buzzer_enabled avec
rclc_parameter_get_bool avant d’agir, et répond success = false s’il
est désactivé.
Pourquoi l’ordre déclarer, puis valeur. rclc_add_parameter crée le
paramètre, sans valeur ; le premier rclc_parameter_set_* la lui donne,
et passe par le rappel de validation avec old_param à NULL. Un
rappel qui refuse ce cas laisserait tous les paramètres à zéro, sans aucun
message : c’est un piège rencontré sur le firmware du robot.
max_params : par défaut, le serveur n’accepte que quatre
paramètres ; au-delà, rclc_add_parameter échoue en silence si l’on ne
teste pas son retour. Le robot en déclare treize, d’où l’option.
La validation sur la carte. C’est la carte qui sait quelles valeurs
sont dangereuses pour elle. Valider côté PC serait insuffisant : n’importe
quel outil ROS 2 peut appeler le service set_parameters.
Réponses aux questions 4 à 6
4. La valeur 10. ros2 param set répond « Setting parameter
failed », et ros2 param get montre que la valeur est restée à 100. Le
refus a été décidé sur la carte, par on_param_changed, qui a renvoyé
false : la requête a traversé l’agent, le serveur de paramètres l’a
soumise au rappel, et a renvoyé le refus au PC.
5. Après un redémarrage. Les paramètres reviennent à leurs valeurs de
départ (500, 35,0, vrai) : ils vivent en RAM. Le firmware du robot
garde les siens (rayons des roues, entraxe, échelle du gyroscope…) dans sa
flash de données SPI : il les y relit au démarrage, avant de créer son
nœud, et les y réécrit à la demande (ratbot config save). Un réglage
d’étalonnage qui disparaît à chaque coupure serait inutilisable.
6. Les cinq services. ros2 service list montre, sous
/tp/ratbot_lab/ :
describe_parameters get_parameter_types get_parameters
list_parameters set_parameters
Ce sont les services standard que tout nœud ROS 2 expose pour ses
paramètres ; c’est par eux que passent ros2 param, rqt ou tout autre
outil. Le serveur ajoute un éditeur, parameter_events, qui annonce
chaque changement. rclc n’implémente pas le sixième service des nœuds ROS 2
complets, set_parameters_atomically, pour économiser la mémoire.
Tout le code de la correction
taches.c — compilé sans
avertissement contre les en-têtes de micro-ROS Humble, la HAL et FreeRTOS
du firmware RatBot (sans édition de liens ni essai sur carte).
Le fichier contient les inclusions, les déclarations et la fonction de la
tâche micro-ROS, qui remplace le squelette StartDefaultTask généré par
CubeMX. La configuration attendue est rappelée en tête du fichier, et les
réponses aux questions à la fin.
Exercices supplémentaires
Ces exercices n’ont pas de correction.
Mémoriser les réglages : un service
saveécrit les paramètres dans un registre de sauvegarde de la RTC (TP 9 de la partie 1) ; au démarrage, ils y sont relus.Un fichier de paramètres :
ros2 param dumpécrit les paramètres dans un fichier YAML,ros2 param loadles recharge. Essayez.Les contraintes déclarées : utilisez
rclc_add_parameter_constraint_integerpour déclarer la plage deblink_period_ms, et observezros2 param describe.
Après le TP
La flash externe contient votre programme : réinstallez celui du robot, voir Remettre la carte en état RatBot.