···228228229229En 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
230230231231-#figure(
231231+#grid(
232232+ columns: 2,
233233+ gutter: 1em,
234234+figure(
232235 caption: [Création d'un _subscriber_ à `rt/lowcmd` dans `UnitreePlugin::Configure`],
233233-```cpp
236236+ text(size: 0.8em, ```cpp
234237auto subscriber = ChannelSubscriberPtr<LowCmd_>(
235238 new ChannelSubscriber<LowCmd_>("rt/lowcmd")
236239);
···242245)
243246244247subscriber->InitChannel(handler, 1);
245245-```
246246-)
247248248248-== Réception des commandes <receive-lowcmd>
249249+.
250250+```)
251251+),
249252250250-Lorsqu'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.
253253+figure(
254254+ caption: [Création du _publisher_ pour `rt/lowstate` dans `UnitreePlugin::Configure`],
255255+text(size: 0.8em, ```cpp
256256+auto publisher = ChannelPublisherPtr<LowState_>(
257257+ new ChannelPublisher<LowState_>("rt/lowstate")
258258+);
251259260260+publisher->InitChannel();
252261253253-Ensuite, Gazebo démarre un nouveau pas de simulation. Avant de faire ce pas, il appelle la méthode `::PreUpdate` sur notre plugin, qui vient chercher la commande stockée dans le _buffer_ (1B), et applique cette commande sur le modèle du robot, animé par le simulateur.
262262+this->publisher_thread = CreateRecurrentThreadEx(
263263+ "low_state_writer",
264264+ UT_CPU_ID_NONE,
265265+ 500,
266266+ &UnitreePlugin::LowStateWriter,
267267+ this
268268+);
269269+```)
270270+)
271271+)
254272255255-Pour appliquer la commande, on calcule la force effective que le moteur doit appliquer:
273273+== `rt/lowcmd`
274274+275275+=== Calcul des nouvelles forces des moteurs
276276+277277+Pour appliquer une commande à un moteur, on calcule la force effective que le moteur doit appliquer:
256278257279$
258280tau =
···305327 joint.SetForce(ecm, torque);
306328```
307329330330+=== Réception des commandes <receive-lowcmd>
331331+332332+Lorsqu'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.
333333+334334+335335+Ensuite, Gazebo démarre un nouveau pas de simulation. Avant de faire ce pas, il appelle la méthode `::PreUpdate` sur notre plugin, qui vient chercher la commande stockée dans le _buffer_ (1B), et applique cette commande sur le modèle du robot, animé par le simulateur.
336336+337337+308338#architecture([Phase de réception des commandes], {
309339 edge(<policy>, (2, -1), (2, 0), "-->", label-pos: 10%)[(1A) publish]
310340 edge(<policy>, (2, -1), (2, 0), stroke: none, label-pos: 60%, label-side: left)[(1A) subscription]
···383413// ]
384414385415386386-== Émission de l'état <send-lowstate>
416416+== `rt/lowstate`
417417+418418+=== Construction d'un message `rt/lowstate`
419419+420420+La documentation d'Unitree liste l'ensemble des champs disponibles dans un message `rt/lowstate`, c'est-à-dire l'ensemble des données que l'on doit récupérer afin de construire nos messages d'état @h1-rt-lowstate:
421421+422422+#table(
423423+ columns: (1.5fr, 0.5fr, 3fr, 2fr),
424424+ stroke: none,
425425+ inset: 8pt,
426426+427427+ "Champ", "Type", "Description", "Où récupérer la valeur",
428428+ table.hline(),
429429+430430+ `version`, $NN^2$, [Tuple représentation la version d'Unitree], [],
431431+ `mode_pr`, ${0, 1}$, [Défini sur 0 par défaut], [],
432432+ `mode_machine`, ${4, 6}$, [Défini sur 6 par défaut], [],
433433+ `tick`, $NN$, [Non documenté], [],
434434+ `wireless_remote`, ${0, 1}^(40)$, [Non documenté], [],
435435+ `reserve`, $NN^4$, [Non documenté], [],
436436+ `crc`, $NN$, [Somme de contrôle du message, utilisant _CRC32_. Une implémentation ad-hoc existe dans le code source de `unitree_sdk2` et de `unitree_mujoco` #todo[Mettre en annexe ?]], [Copié-collé de l'implémentation d'Unitree],
437437+ `imu_state`, `IMUState`, [Valeurs des capteurs intertiels du robot],
438438+ `imu_state.quaternion`, $RR^4$, [Posture dans l'espace du robot, dans l'ordre $(w, x, y, z)$],
439439+ `imu_state.rpy`, $RR^3$, [Angle d'Euler du robot, dans l'ordre $(r, p, y)$],
440440+ `imu_state.gyroscope`, $$
441441+)
442442+443443+444444+=== Émission de l'état <send-lowstate>
387445388446Avant de démarrer un nouveau pas de simulation, la méthode `::PreUpdate` vient mettre à jour l'état du robot simulé en modifiant le _State buffer_ interne au plugin (1A).
389447390390-Le `LowStateWriter` vient lire le _State buffer_ (1B) pour publier l'état sur le canal DDS (2, 3) qui est ensuite lu par $cal(P)$ (4), qui (on le suppose) poss-de une subscription sur `rt/lowstate`
448448+Le `LowStateWriter` vient lire le _State buffer_ (1B) pour publier l'état sur le canal DDS (2, 3) qui est ensuite lu par $cal(P)$ (4), qui (on le suppose) possède une subscription sur `rt/lowstate`
449449+391450392451#let transparent = luma(0).opacify(0%)
393393-394452395453#architecture([Phase d'envoi de l'état], {
396454 edge(<preupdate>, "d,d,r", <statebuf>, "->")[(1A)]
···418476]) $cal(P)$ sera moins grande
419477/ Si `::PreUpdate` est moins fréquente: $cal(P)$ reçevra plusieurs fois le même état, ce qui sera représentatif du fait que la simulation n'a pas encore avancé.
420478479479+421480== Désynchronisations
422481423482Dans un même appel de `::PreUpdate`, on effectue d'abord la mise à jour du _State buffer_, puis on lit dans le _Commands buffer_.
···426485427486- Celle de la simulation (en bleu), qui doit englober l'entièreté d'un cycle
428487- Celle du `ChannelPublisher` (en rouge)
429429-- Celle de $cal(P)$ (en rose)
488488+- Celle de $cal(P)$ (en vert)
430489431490#architecture([Cycle complet. Un cycle commence avec la flèche "update" partant de `::PreUpdate`], {
432491 let colored-edge = (color, label, ..args) => edge(stroke: color, label: text(fill: color, label), ..args)
433492 let sim-edge = (label, ..args) => colored-edge(blue, label, ..args)
434493 let publisher-edge = (label, ..args) => colored-edge(red, label, ..args)
435435- let policy-edge = (label, ..args) => colored-edge(fuchsia, label, ..args)
494494+ let policy-edge = (label, ..args) => colored-edge(olive.darken(30%), label, ..args)
436495437496 // Simulation loop
438497 sim-edge("read", <preupdate>, "d,d,r,r", <cmdbuf>, "<-@")
···463522464523== Amélioration des performances <perf>
465524466466-Les premiers essais montrent un
525525+Les premiers essais affichent un
467526468527== Enregistrement de vidéos <video>
469528