···197197198198 node(name: <lowstate>, (1, 2), `::LowStateWriter`)
199199 node(name: <lowcmd>, (2, 2), `::CmdHandler`)
200200- node(name: <statebuf>, (1, 3))[State buffer]
201201- node(name: <cmdbuf>, (2, 3))[Commands buffer]
200200+ node(name: <statebuf>, (1, 3), subtitled("State buffer", `statebuf`))
201201+ node(name: <cmdbuf>, (2, 3), subtitled("Commands buffer", `cmdbuf`))
202202 group((<lowstate>, <lowcmd>, <statebuf>, <cmdbuf>))[Plugin internals]
203203204204 node(name: <policy>, (0, -1), $cal(P)$)
···213213 edge(<configure>, "u", <channelfactory>, "->", label-side: left, label-pos: 50%)[appelle]
214214 edge(<channelfactory>, "->", <publisher>)[initialise]
215215 edge(<channelfactory>, "->", <subscriber>)[initialise]
216216- edge(<publisher>, "<->", <lowstate>)[associés]
217217- edge(<subscriber>, "<->", <lowcmd>)[associés]
216216+ edge(<publisher>, "<->", <lowstate>)[`std::bind`]
217217+ edge(<subscriber>, "<->", <lowcmd>)[`std::bind`]
218218})
219219220220On commence par instancier un contrôleur dans le domaine DDS n°1, sur l'interface réseau `lo`#footnote[interface dite "loopback", qui est locale à l'ordinateur: ici, le simulateur et la politique de contrôle tournent sur la même machine, donc les messages DDS n'ont pas besoin de "sortir" de celle-ci]
···226226227227Cette initialisation est faite à l'initialisation du plugin par Gazebo, en la faisant dans la méhode `::Configure` du plugin.
228228229229+En pratique, on utilise `std::bind` @cpp-bind pour fixer l'instance d'`UnitreePlugin` et ainsi passer des méthodes de la classe comme des simples fonctions
230230+231231+#figure(
232232+ caption: [Création d'un _subscriber_ à `rt/lowcmd` dans `UnitreePlugin::Configure`],
233233+```cpp
234234+auto subscriber = ChannelSubscriberPtr<LowCmd_>(
235235+ new ChannelSubscriber<LowCmd_>("rt/lowcmd")
236236+);
237237+238238+auto handler = std::bind(
239239+ &UnitreePlugin::CmdHandler,
240240+ this,
241241+ std::placeholders::_1
242242+)
243243+244244+subscriber->InitChannel(handler, 1);
245245+```
246246+)
247247+229248== Réception des commandes <receive-lowcmd>
230249231250Lorsqu'un message, publié par $cal(P)$ (1A) et contenant des ordres pour les moteurs, arrive sur `rt/lowcmd`, `::CmdHandler` est appelé (2, 3), et modifie un _buffer_ (4) contenant la dernière commande reçue.
···235254236255Pour appliquer la commande, on calcule la force effective que le moteur doit appliquer:
237256238238-#grid(
239239- columns: 2,
240240- gutter: 1em,
241241- [
242242- #math.equation(block: true, numbering: none, $
243243- tau =
244244- underbracket(K_p Delta q, "proportional") +
245245- underbracket(tau_"ff", "integrative") +
246246- underbracket(K_d Delta dot(q), "derivative")
247247- $)
257257+$
258258+tau =
259259+ underbracket(K_p Delta q, "proportional") +
260260+ underbracket(tau_"ff", "integrative") +
261261+ underbracket(K_d Delta dot(q), "derivative")
262262+$
248263249249- Avec
264264+Avec
250265251251- / $tau$: pour _torque_, la force à donner au moteur
252252- / $tau_"ff"$: le $tau$ "feed-forward", #todo[I de PID ou pas?]
253253- / $Delta q$: écart d'angle de rotation du moteur entre la consigne et l'état actuel
254254- / $Delta dot(q)$: écart de vitesse (instantanée) de rotation du moteur entre la consigne et l'état actuel
255255- / $K_p$, $K_d$: coefficients modulant la prépondérance de $Delta dot(q)$ et $Delta dot(q)$
256256- ],
257257- text(size: 0.9em,
258258- ```cpp
266266+/ $tau$: pour _torque_, la force à donner au moteur
267267+/ $tau_"ff"$: le $tau$ "feed-forward", #todo[I de PID ou pas?]
268268+/ $Delta q$: écart d'angle de rotation du moteur entre la consigne et l'état actuel
269269+/ $Delta dot(q)$: vitesse de changement de la consigne#footnote[
270270+271271+#let ddt = derivee => $ ( op("d") #derivee ) / ( op("d") t ) $
272272+273273+On a bien $ddt(Delta q) = Delta dot(q)$ par linéarité de la dérivation temporelle:
274274+275275+$
276276+ddt(Delta q) = ddt(q_"new" - q_"old") = ddt(q_"new") - ddt(q_"old") = Delta ddt(q) = Delta dot(q)
277277+$
278278+279279+]
280280+/ $K_p$: prépondérance de la partie proportionelle
281281+/ $K_p$: prépondérance de la partie dérivée
282282+283283+Cette équation met à jour $tau$ pour rapprocher l'état actuel du moteur de la nouvelle consigne, en prenant en compte
284284+285285+- L'erreur sur l'angle $Delta q$ (partie "proportional")
286286+- L'erreur sur la vitesse de changement de $Delta q$ (partie "derivative")
287287+288288+Cette prise en compte de la vitesse permet de lisser les changements appliqués aux moteurs @control-pid.
289289+290290+On contrôle la proportion de chaque terme dans le calcul de la nouvelle consigne grâce à deux coefficients, $K_p$ et $K_d$.
291291+292292+En pratique, les valeurs actuelles pour le calcul de $Delta q$ et $Delta dot(q)$ proviennent de l'état du moteur, accessible dans `rt/lowstate` avec les champs `q` et `dq` du moteur en question @h1-rt-lowstate
293293+294294+```cpp
259295 // Avec i l'indice du moteur
260260- auto force =
261261- cmdbuf->tau_ff.at(i) +
262262- cmdbuf->kp.at(i) * (
263263- cmdbuf->q_target.at(i) -
264264- lowstate.motor_state().at(i).q()
265265- ) +
266266- cmdbuf->kd.at(i) * (
267267- cmdbuf->dq_target.at(i) -
268268- lowstate.motor_state().at(i).dq()
296296+ auto force = cmdbuf->tau_ff.at(i) + // tau_ff
297297+ cmdbuf->kp.at(i) * ( // K_p
298298+ cmdbuf->q_target.at(i) - lowstate.motor_state().at(i).q() // Delta q
299299+ ) +
300300+ cmdbuf->kd.at(i) * ( // K_d
301301+ cmdbuf->dq_target.at(i) - lowstate.motor_state().at(i).dq() // Delta q.
269302 );
270303271304 std::vector<double> torque = {force};
272305 joint.SetForce(ecm, torque);
273273- ```),
274274- "",
275275- align(center)[Implémentation]
276276-)
306306+```
277307278308#architecture([Phase de réception des commandes], {
279309 edge(<policy>, (2, -1), (2, 0), "-->", label-pos: 10%)[(1A) publish]