<?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>Pyusb on Guillaume Delré</title><link>https://guillaumedelre.github.io/tags/pyusb/</link><description>Recent content in Pyusb on Guillaume Delré</description><generator>Hugo</generator><language>en</language><lastBuildDate>Tue, 21 Feb 2017 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/tags/pyusb/index.xml" rel="self" type="application/rss+xml"/><item><title>Controlling a USB missile launcher over HTTP with FastAPI and Docker</title><link>https://guillaumedelre.github.io/2017/02/21/controlling-a-usb-missile-launcher-over-http-with-fastapi-and-docker/</link><pubDate>Tue, 21 Feb 2017 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/2017/02/21/controlling-a-usb-missile-launcher-over-http-with-fastapi-and-docker/</guid><description>How we wired a USB foam missile launcher to the CI pipeline — and what Docker, udev, and WSL2 had to say about it.</description><content:encoded><![CDATA[<p>The rule was simple: whoever breaks the CI build owes the team a coffee. It worked fine for a while. Then someone suggested we needed something with more immediate feedback. Something physical. Something that fires.</p>
<p>A <a href="http://www.dreamcheeky.com/thunder-missile-launcher" target="_blank" rel="noopener noreferrer">Dream Cheeky Thunder</a> appeared on a desk shortly after. Four foam missiles, a USB cable, and a very clear team consensus: hook it to the cluster, wire it to the build pipeline, and let the CI decide who deserves a volley.</p>
<p>The launcher needed to respond to HTTP calls from anywhere on the network. No driver, no GUI, no manual aiming. Just an endpoint that makes it shoot in the direction of the guilty party&rsquo;s desk.</p>
<p>This is the story of <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="no-sdk-no-docs-no-problem">No SDK, no docs, no problem</h2>
<p>Dream Cheeky never published a protocol spec. The launcher speaks raw USB HID, and the only starting point was a vendored Python script from 2012 floating around in forum threads. Vendor ID <code>0x2123</code>, product ID <code>0x1010</code>, and a handful of control bytes that someone had reverse engineered years before.</p>
<p>That was enough. The protocol is simple: send a byte sequence to move the motors, send another to fire. The tricky part is that the launcher has no position feedback. No encoders, no limit switches beyond the physical hard stops at the extremes. You drive it blind.</p>
<h2 id="from-usb-to-http">From USB to HTTP</h2>
<p>The CI pipeline needed to trigger the launcher over the network. A local script wasn&rsquo;t going to cut it — the launcher had to be reachable from any machine on the cluster, including the build server. So: a REST API.</p>
<p>FastAPI was the obvious choice. The targeting flow from the CI side ends up being three HTTP calls:</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 to known position</span>
</span></span><span style="display:flex;"><span>curl -X POST http://localhost:8000/yaw/20    <span style="color:#75715e"># rotate toward guilty desk</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>The <code>/park</code> call matters more than it looks. Since the launcher has no position feedback, the server estimates the current angle by tracking how long the motors have been running. That estimate drifts. Bumping the hardware, interrupting a command, or just the imprecision of time-based tracking — they all accumulate. Parking drives both motors against the physical hard stops at full sweep, which guarantees alignment regardless of what the server thinks it knows. Skip it, and your aim is a guess.</p>
<p>The full API reference is <a href="https://github.com/guillaumedelre/dream-cheeky-thunder/blob/develop/docs/api.md" target="_blank" rel="noopener noreferrer">in the repo</a>. There&rsquo;s also a web UI if you prefer clicking over <code>curl</code>.</p>
<h2 id="docker-knows-nothing-about-usb">Docker knows nothing about USB</h2>
<p>Running this in a Docker container on the cluster was where the fun really started: containers don&rsquo;t see USB devices by default.</p>
<p>The <code>devices</code> mount in <code>compose.yaml</code> exposes the USB bus to the container:</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>Not enough. First run came back with <code>USBError: [Errno 13] Access denied</code>. The device node is there inside the container, but it inherits permissions from the host, and on the host only root can open it by default.</p>
<p>The fix is a udev rule. Drop one file into <code>/etc/udev/rules.d/</code>, and the kernel sets the right group and permissions when the device plugs in. After that, the container user can open it without needing elevated privileges. The rule ships with the project, setup instructions are <a href="https://github.com/guillaumedelre/dream-cheeky-thunder/blob/develop/docs/setup-linux.md" target="_blank" rel="noopener noreferrer">in the docs</a>.</p>
<h2 id="wsl2-made-it-interesting">WSL2 made it interesting</h2>
<p>Half the team runs Windows with Docker Desktop on WSL2. That&rsquo;s where things got creative.</p>
<p>WSL2 has no access to USB devices by default: the Windows kernel holds them, and the <code>devices</code> mount alone does nothing because WSL2 simply doesn&rsquo;t see the hardware. The fix is <a href="https://github.com/dorssel/usbipd-win" target="_blank" rel="noopener noreferrer">usbipd-win</a>, which forwards the USB device from Windows into the WSL2 kernel over IP. Once that&rsquo;s done, the Linux path works exactly the same: udev rule, <code>devices</code> mount, done.</p>
<p>The attachment doesn&rsquo;t survive reboots, though. usbipd v4+ added a policy mechanism that automates reconnection, which killed the &ldquo;it worked yesterday&rdquo; mystery that had been annoying us for days.</p>
<h2 id="what-actually-surprised-us">What actually surprised us</h2>
<p><strong>Time-based positioning works well enough.</strong> No encoders meant we went in expecting the angle tracking to be basically useless. Turns out, parking before every sequence kept it accurate enough to reliably aim at a specific desk. Not millimeter precision, but foam missile precision is fine.</p>
<p><strong>The <code>devices</code> mount is necessary but not sufficient.</strong> The permission error was confusing precisely because the device was clearly visible inside the container. The udev rule is the bit most tutorials quietly skip.</p>
<p><strong>The coffee rule was never the same after this.</strong> Once the launcher was wired to the pipeline, broken builds suddenly became a lot more motivating to fix.</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 — HTTP control for the Dream Cheeky Thunder USB missile launcher. Pull requests welcome, especially if you have a better angle calibration approach.</p>
</div>
]]></content:encoded></item></channel></rss>