<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Wsl2 on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/wsl2/</link><description>Recent content in Wsl2 on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Tue, 21 Feb 2017 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/wsl2/index.xml" rel="self" type="application/rss+xml"/><item><title>Contrôler un lance-missiles USB en HTTP avec FastAPI et Docker</title><link>https://guillaumedelre.github.io/fr/2017/02/21/contr%C3%B4ler-un-lance-missiles-usb-en-http-avec-fastapi-et-docker/</link><pubDate>Tue, 21 Feb 2017 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2017/02/21/contr%C3%B4ler-un-lance-missiles-usb-en-http-avec-fastapi-et-docker/</guid><description>Comment on a branché un lance-missiles USB en mousse sur le pipeline CI — et ce que Docker, udev et WSL2 avaient à dire là-dessus.</description><content:encoded><![CDATA[<p>La règle était simple : celui qui casse le build CI offre le café à l&rsquo;équipe. Ça a marché un moment. Puis quelqu&rsquo;un a proposé qu&rsquo;on ait un retour plus immédiat. Quelque chose de physique. Quelque chose qui tire.</p>
<p>Un <a href="http://www.dreamcheeky.com/thunder-missile-launcher" target="_blank" rel="noopener noreferrer">Dream Cheeky Thunder</a> a atterri sur un bureau peu après. Quatre missiles en mousse, un câble USB, et un consensus d&rsquo;équipe très clair : le brancher au cluster, le câbler au pipeline de build, et laisser le CI décider qui mérite une volée.</p>
<p>Le lanceur devait répondre à des appels HTTP depuis n&rsquo;importe où sur le réseau. Sans driver, sans GUI, sans visée manuelle. Juste un endpoint qui le fait tirer dans la direction du bureau du coupable.</p>
<p>Voilà l&rsquo;histoire de <a href="https://github.com/guillaumedelre/dream-cheeky-thunder" target="_blank" rel="noopener noreferrer">dream-cheeky-thunder</a>.</p>
<p><img alt="Dream Cheeky Thunder" loading="lazy" src="https://raw.githubusercontent.com/guillaumedelre/dream-cheeky-thunder/develop/docs/Dream-Cheeky-Thunder.jpg"></p>
<h2 id="pas-de-sdk-pas-de-docs-pas-de-problème">Pas de SDK, pas de docs, pas de problème</h2>
<p>Dream Cheeky n&rsquo;a jamais publié de spec de protocole. Le lanceur parle USB HID brut, et le seul point de départ était un script Python vendorisé de 2012 qui traînait dans des fils de forum. Vendor ID <code>0x2123</code>, product ID <code>0x1010</code>, et une poignée d&rsquo;octets de contrôle que quelqu&rsquo;un avait rétro-ingénié des années auparavant.</p>
<p>C&rsquo;était suffisant. Le protocole est simple : envoyer une séquence d&rsquo;octets pour bouger les moteurs, en envoyer une autre pour tirer. La partie délicate : le lanceur n&rsquo;a aucun retour de position. Pas d&rsquo;encodeurs, pas de fins de course en dehors des butées physiques aux extrémités. On le pilote à l&rsquo;aveugle.</p>
<h2 id="du-usb-au-http">Du USB au HTTP</h2>
<p>Le pipeline CI devait déclencher le lanceur par le réseau. Un script local ne suffisait pas — le lanceur devait être accessible depuis n&rsquo;importe quelle machine du cluster, y compris le serveur de build. Donc : une API REST.</p>
<p>FastAPI était le choix évident. Le flux de ciblage côté CI se résume à trois appels HTTP :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -X POST http://localhost:8000/park      <span style="color:#75715e"># reset vers une position connue</span>
</span></span><span style="display:flex;"><span>curl -X POST http://localhost:8000/yaw/20    <span style="color:#75715e"># rotation vers le bureau du coupable</span>
</span></span><span style="display:flex;"><span>curl -X POST <span style="color:#e6db74">&#34;http://localhost:8000/fire?shots=2&#34;</span>
</span></span></code></pre></div><p>L&rsquo;appel <code>/park</code> est plus important qu&rsquo;il n&rsquo;y paraît. Puisque le lanceur n&rsquo;a pas de retour de position, le serveur estime l&rsquo;angle courant en suivant le temps de rotation des moteurs. Cette estimation dérive. Un choc sur le hardware, une commande interrompue, ou simplement l&rsquo;imprécision du tracking temporel — tout s&rsquo;accumule. Le parking pousse les deux moteurs contre les butées physiques en balayage complet, ce qui garantit l&rsquo;alignement quelle que soit la représentation interne du serveur. Sans ça, la visée est une approximation.</p>
<p>La référence complète de l&rsquo;API est <a href="https://github.com/guillaumedelre/dream-cheeky-thunder/blob/develop/docs/api.md" target="_blank" rel="noopener noreferrer">dans le repo</a>. Il y a aussi une UI web si vous préférez cliquer plutôt que <code>curl</code>.</p>
<h2 id="docker-ne-connaît-pas-lusb">Docker ne connaît pas l&rsquo;USB</h2>
<p>Faire tourner ça dans un conteneur Docker sur le cluster, c&rsquo;est là que les choses ont commencé à devenir intéressantes : les conteneurs ne voient pas les périphériques USB par défaut.</p>
<p>Le mount <code>devices</code> dans <code>compose.yaml</code> expose le bus USB au conteneur :</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">devices</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">/dev/bus/usb:/dev/bus/usb</span>
</span></span></code></pre></div><p>Pas suffisant. Première exécution : <code>USBError: [Errno 13] Access denied</code>. Le nœud de device est bien là dans le conteneur, mais il hérite des permissions du host, et sur le host seul root peut l&rsquo;ouvrir par défaut.</p>
<p>La solution : une règle udev. Déposer un fichier dans <code>/etc/udev/rules.d/</code>, et le kernel applique le bon groupe et les bonnes permissions quand le device se branche. Après ça, l&rsquo;utilisateur du conteneur peut l&rsquo;ouvrir sans privilèges élevés. La règle est fournie avec le projet, les instructions d&rsquo;installation sont <a href="https://github.com/guillaumedelre/dream-cheeky-thunder/blob/develop/docs/setup-linux.md" target="_blank" rel="noopener noreferrer">dans la doc</a>.</p>
<h2 id="wsl2-a-rendu-ça-intéressant">WSL2 a rendu ça intéressant</h2>
<p>La moitié de l&rsquo;équipe tourne sous Windows avec Docker Desktop sur WSL2. C&rsquo;est là que ça devient créatif.</p>
<p>WSL2 n&rsquo;a pas accès aux périphériques USB par défaut : le kernel Windows les détient, et le mount <code>devices</code> seul ne fait rien parce que WSL2 ne voit simplement pas le hardware. La solution est <a href="https://github.com/dorssel/usbipd-win" target="_blank" rel="noopener noreferrer">usbipd-win</a>, qui transfère le périphérique USB de Windows vers le kernel WSL2 par IP. Une fois ça fait, le chemin Linux fonctionne à l&rsquo;identique : règle udev, mount <code>devices</code>, terminé.</p>
<p>L&rsquo;attachement ne survit pas aux redémarrages, cependant. usbipd v4+ a ajouté un mécanisme de policy qui automatise la reconnexion, ce qui a mis fin au mystère du &ldquo;ça marchait hier&rdquo; qui nous agaçait depuis des jours.</p>
<h2 id="ce-qui-nous-a-vraiment-surpris">Ce qui nous a vraiment surpris</h2>
<p><strong>Le positionnement temporel fonctionne suffisamment bien.</strong> Sans encodeurs, on s&rsquo;attendait à ce que le tracking d&rsquo;angle soit quasi-inutilisable. En pratique, le parking avant chaque séquence le maintenait assez précis pour viser un bureau spécifique de manière fiable. Pas au millimètre, mais la précision missile en mousse, ça convient.</p>
<p><strong>Le mount <code>devices</code> est nécessaire mais pas suffisant.</strong> L&rsquo;erreur de permission était déroutante précisément parce que le device était clairement visible dans le conteneur. La règle udev est la partie que la plupart des tutoriels passent discrètement sous silence.</p>
<p><strong>La règle café n&rsquo;a plus jamais été la même après ça.</strong> Une fois le lanceur câblé au pipeline, les builds cassés sont devenus beaucoup plus motivants à corriger.</p>
<div style="border: 1px solid #e8e8e8; padding: 16px; margin-top: 2em; border-radius: 3px;">
  <img src="https://cdn.simpleicons.org/github" width="20" style="vertical-align: middle; margin-right: 8px;" />
  <strong><a href="https://github.com/guillaumedelre/dream-cheeky-thunder" target="_blank" rel="noopener noreferrer">guillaumedelre/dream-cheeky-thunder</a></strong>
  <p style="margin: 8px 0 0; color: #828282; font-size: 14px;">FastAPI + Docker + PyUSB — contrôle HTTP pour le lance-missiles USB Dream Cheeky Thunder. Pull requests bienvenus, surtout si vous avez une meilleure approche de calibration d'angle.</p>
</div>
]]></content:encoded></item></channel></rss>