<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[breq.dev]]></title>
        <description><![CDATA[hey, i'm brooke. this feed tracks blog posts and project writeups.]]></description>
        <link>https://breq.dev</link>
        <image>
            <url>https://breq.dev/rss.png</url>
            <title>breq.dev</title>
            <link>https://breq.dev</link>
        </image>
        <generator>node-rss</generator>
        <lastBuildDate>Sun, 29 Mar 2026 14:32:05 GMT</lastBuildDate>
        <atom:link href="https://breq.dev/rss.xml" rel="self" type="application/rss+xml"/>
        <pubDate>Sun, 29 Mar 2026 14:32:05 GMT</pubDate>
        <copyright><![CDATA[All rights reserved, Brooke Chalmers 2026]]></copyright>
        <language><![CDATA[en]]></language>
        <managingEditor><![CDATA[breq@breq.dev (Brooke Chalmers)]]></managingEditor>
        <webMaster><![CDATA[breq@breq.dev (Brooke Chalmers)]]></webMaster>
        <item>
            <title><![CDATA[Playing with Wii Extension Controllers]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I recently spent some time writing a driver for Wii accessories (like the Nunchuck and Classic Controller). I&#x27;ve published it on crates.io as <a href="https://crates.io/crates/wii-accessories" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">wii-accessories</code></a>. Here&#x27;s a deep dive into why I chose to do this and how these controllers work under the hood!</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-search-for-a-joystick">The search for a joystick</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Recently, I was working on a project and looking for an easy way to add joystick controllers. Broadly, solutions to this problem fall under three categories.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first category is USB-based controllers. While more microcontrollers are starting to support USB host mode, the setup required for these is still quite difficult, and the controllers themselves can be relatively expensive. These could be a good fit for projects with a full Linux system, but my project only had a microcontroller.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The second category is purpose-built controllers designed to be hooked up to microcontrollers, like the <a href="https://www.sparkfun.com/sparkfun-qwiic-joystick.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Sparkfun Quiic Joystick</a> or <a href="https://www.adafruit.com/product/5743" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit STEMMA Gamepad</a>. These have good library support, but aren&#x27;t as ergonomic as an actual consumer controller and don&#x27;t come with an enclosure. They certainly fill a niche, but aren&#x27;t what I&#x27;m looking for.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This brings us to the third category: controllers built for older consoles. Unfortunately, most of these are hard to use in practice. The NES and SNES, for instance, used a shift-register API that&#x27;s easy for any modern microcontroller to implement, but connectors for them are hard to come by and they lack an analog stick. The PlayStation used a protocol that&#x27;s <a href="https://hackaday.io/project/170365-blueretro/log/186471-playstation-playstation-2-spi-interface" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><em>almost</em> SPI</a> but with a non-standard additional pin for data acknowledgement. The N64 and GameCube used a bespoke half-duplex serial protocol which is <a href="https://www.qwertymodo.com/hardware-projects/n64/n64-controller" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">tricky to implement with standard peripherals</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All is not lost, though. As it turns out, the Wii Nunchuck manages to be almost perfect for this use case:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Standard I2C interface.</strong> The Nunchuck connector has just 5 pins: VCC, GND, SDA, SCL, and a presence detection pin. It can even happily share a bus with other I2C devices.</li>
<li class="my-2 pl-2"><strong>3.3V Power.</strong> In an era where the vast majority of microcontrollers have moved to 3.3V power and logic, this eliminates the need for a separate 5V supply in many projects.</li>
<li class="my-2 pl-2"><strong>(Sorta) easy connectors.</strong> While buying an actual expansion card connector is tricky, the Nunchuck connector can be easily emulated with an edge connector on a PCB.</li>
<li class="my-2 pl-2"><strong>Cheap and easy to find.</strong> You might already have one around, but if not, third-party versions of the controller are easy to find and purchase online.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These benefits also apply to other controllers which use this port such as the Wii Classic Controller, giving even more options for interfacing.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pcb-connector">PCB Connector</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since both the connector pins and locking tabs lie in the same plane, a PCB edge connector can create a solid and reliable connection! Several breakout boards have been designed around this principle and are available for cheap online.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1453" height="1094" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwii-accessories%2Fnunchucky.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwii-accessories%2Fnunchucky.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwii-accessories%2Fnunchucky.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The <a href="https://www.solarbotics.com/product/31040/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Nunchucky</a>, the earliest example I could find of this edge connector technique.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Building this connector into a custom board is also quite straightforward. There&#x27;s a KiCAD footprint available in this <a href="https://gitlab.com/kicad/libraries/kicad-footprints/-/merge_requests/3351" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">merge request</a> that has both the pad spacing and the required board outline cuts to create the connector shape.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1883" height="1883" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwii-accessories%2Fkicad_footprint.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwii-accessories%2Fkicad_footprint.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwii-accessories%2Fkicad_footprint.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Although my girlfriend remarked that &quot;getting a footprint from an open merge request to the KiCAD repo is like copying code from the question part on StackOverflow,&quot; the connector seems to work great on my boards! The fit is best with a 2.0mm thick board, but I went with the standard 1.6mm thickness and found it to still work well.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="implementing-a-rust-driver">Implementing a Rust driver</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While the I2C protocol is not officially documented anywhere, the Wii homebrew community has made excellent progress in reverse-engineering it. Let&#x27;s walk through some code examples in Rust to understand the protocol.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All Wii accessories use an I2C address of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x52</code>. You might assume that the Wii Motion Plus uses a different address from other controllers since it allows daisy-chaining, but Nintendo instead opted to invent their own complex &quot;passthrough&quot; modes of passing through data for reasons unknown.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">u8</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0x52</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Nintendo decided to obfuscate data going over the I2C connection, likely to make it more difficult to reverse-engineer. With this enabled, lookup tables need to be used to de-obfuscate the data. Thankfully, this can be disabled by writing value <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x55</code> to register <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0xF0</code> and then writing <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x00</code> to register <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0xFB</code>, regardless of the connected controller type.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">i2c</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0xF0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x55</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0xFB</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x00</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The six bytes starting at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0xFA</code> identify the controller model:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">i2c</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0xFA</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">unwrap</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#8B5CF6">mut</span><span class=""> identifier </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x00</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">6</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">i2c</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">read</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">mut</span><span class=""> identifier</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">unwrap</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Identifier</th><th class="border border-black p-2 dark:border-white">Controller Model</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">0000 A420 0000</td><td class="border border-gray-500 p-2">Nunchuck</td></tr><tr><td class="border border-gray-500 p-2">0000 A420 0101</td><td class="border border-gray-500 p-2">Classic Controller</td></tr><tr><td class="border border-gray-500 p-2">0100 A420 0101</td><td class="border border-gray-500 p-2">Classic Controller Pro (no analog triggers)</td></tr><tr><td class="border border-gray-500 p-2">...</td><td class="border border-gray-500 p-2"><em>full table on <a href="http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">wiibrew.org</a></em></td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">From here, we can start to read data! Data bytes start at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x00</code> for all extension controller types. A report from the Nunchuck includes joystick data, accelerometer data, and the &quot;C&quot; and &quot;Z&quot; buttons.</p>
<table class="border-collapse font-mono text-sm w-full max-w-xl mx-auto table-fixed"><colgroup><col class="w-14"/><col/><col/><col/><col/><col/><col/><col/><col/></colgroup><thead><tr class="[&amp;&gt;th]:pb-1 [&amp;&gt;th]:text-center [&amp;&gt;th]:text-xs [&amp;&gt;th]:text-gray-400 [&amp;&gt;th]:font-medium [&amp;&gt;th:not(:first-child)]:border-b-2 [&amp;&gt;th:not(:first-child)]:border-gray-700"><th class="text-left"></th><th>7</th><th>6</th><th>5</th><th>4</th><th>3</th><th>2</th><th>1</th><th>0</th></tr></thead><tbody class="border-2 border-gray-700 [&amp;&gt;tr]:border-b [&amp;&gt;tr]:border-gray-200 dark:[&amp;&gt;tr]:border-gray-700 [&amp;&gt;tr:last-child]:border-b-0 [&amp;&gt;tr&gt;td:first-child]:py-1 [&amp;&gt;tr&gt;td:first-child]:pr-3 [&amp;&gt;tr&gt;td:first-child]:text-xs [&amp;&gt;tr&gt;td:first-child]:text-gray-400 [&amp;&gt;tr&gt;td:not(:first-child)]:py-1 [&amp;&gt;tr&gt;td]:text-center [&amp;&gt;tr&gt;td:not(:first-child)]:bg-gray-50 dark:[&amp;&gt;tr&gt;td:not(:first-child)]:bg-gray-800"><tr><td>0x00</td><td colspan="8" class="border-l border-gray-200 dark:border-gray-700">Joystick X</td></tr><tr><td>0x01</td><td colspan="8" class="border-l border-gray-200 dark:border-gray-700">Joystick Y</td></tr><tr><td>0x02</td><td colspan="8" class="border-l border-gray-200 dark:border-gray-700">Accelerometer X [9:2]</td></tr><tr><td>0x03</td><td colspan="8" class="border-l border-gray-200 dark:border-gray-700">Accelerometer Y [9:2]</td></tr><tr><td>0x04</td><td colspan="8" class="border-l border-gray-200 dark:border-gray-700">Accelerometer Z [9:2]</td></tr><tr><td>0x05</td><td colspan="2" class="border-l border-r border-gray-200 dark:border-gray-700">Accel Z [1:0]</td><td colspan="2" class="border-r border-gray-200 dark:border-gray-700">Accel Y [1:0]</td><td colspan="2" class="border-r border-gray-200 dark:border-gray-700">Accel X [1:0]</td><td class="border-r border-gray-200 dark:border-gray-700">C</td><td>Z</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Implementing this in Rust is quite straightforward, albeit tedious:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">self</span><span class="" style="color:#ff218c">.</span><span class="">i2c</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x00</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">unwrap</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#8B5CF6">mut</span><span class=""> result </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x00</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">6</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">self</span><span class="" style="color:#ff218c">.</span><span class="">i2c</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">read</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">WII_ACCESSORY_ADDR</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">mut</span><span class=""> result</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">unwrap</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> x </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x00</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">i16</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#8B5CF6">128</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">i8</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> y </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x01</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">i16</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#8B5CF6">128</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">i8</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> accel_x </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x02</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&lt;&lt;</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#8B5CF6">|</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x05</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0b1100</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> accel_y </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x03</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&lt;&lt;</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#8B5CF6">|</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x05</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0b110000</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">4</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> accel_z </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x04</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&lt;&lt;</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#8B5CF6">|</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x05</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#8B5CF6">u16</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0b11000000</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">6</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> c </span><span class="" style="color:#8B5CF6">=</span><span class=""> result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x05</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0b0010</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">let</span><span class=""> z </span><span class="" style="color:#8B5CF6">=</span><span class=""> result</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0x05</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0b0001</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most of the other controller options follow quite naturally -- while the data is often arranged into messages in a jumbled fashion, the community has done a good enough job with documentation that writing an implementation is straightforward.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="go-forth-and-have-fun">Go forth and have fun</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In just a few lines of code, we&#x27;ve transformed an old game console accessory into a capable controller that&#x27;s easy to use in your next project. It certainly isn&#x27;t as ergonomic or featureful as a modern USB gamepad, but it&#x27;s a great option for wiring directly to a microcontroller. It ended up being perfect for an ESP32-based project I&#x27;m currently working on (which I&#x27;ll share more about in a future post!)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you have some Wii accessories lying around and a project that could use them, maybe give <a href="https://crates.io/crates/wii-accessories" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">my Rust crate</a> a try! If you build something with it, I&#x27;d love to hear about it.</p></div>]]></description>
            <link>https://breq.dev/2026/03/15/wii-accessories</link>
            <guid isPermaLink="false">/2026/03/15/wii-accessories</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[embedded]]></category>
            <category><![CDATA[rust]]></category>
            <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Vector Text Rendering Toolkit]]></title>
            <description><![CDATA[<div class="e-content font-body"><img class="mx-auto" src="/images/vector-text/output_fonts.svg" alt="" width="503" height="385"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wrote a <a href="https://crates.io/crates/vector-text" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Rust library</a> for rendering text to a set of points using commonly available vector fonts.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="why-vector-fonts">Why vector fonts?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most font definitions in our modern world are based on filled shapes. For both printing and text rendering applications, this makes sense. At the end of the day, 99% of all text written on a computer is going to be rasterized into discrete pixels based on whether each pixel is &quot;inside&quot; or &quot;outside&quot; the shape.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, there are still applications out there where drawing filled shapes isn&#x27;t desirable. Devices such as X/Y plotters, vector CRT displays, laser cutters, and galvo-based laser projectors can only draw strokes. In many cases, depending on the drawing APIs available, it is simply easier to work with drawing text based on a vector font.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hershey-fonts">Hershey Fonts</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of the rather obvious things that one might want to do with a display is, of course, render text with it. For this, we need a font. Even though traditional fonts are based on vector graphics, they make use of filled shapes of varying width which can&#x27;t be replicated on a vector display.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In 1967, the U.S. Naval Weapons Laboratory published a font designed for use with vector displays named after its author Dr. Hershey. The <a href="https://en.wikipedia.org/wiki/Hershey_fonts" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Hershey font</a> contained various styles for Latin, Greek, Cyrillic, and Japanese characters. The font was originally developed for display on early cathode ray tube displays which pointed the electron beam based on X and Y signals instead of forming a constant raster pattern as later CRTs did. You might recognize it as the font used by <a href="https://docs.opencv.org/3.1.0/d0/de1/group__core.html#ga0f9314ea6e35f99bb23f29567fc16e11" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OpenCV</a> by default.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Hershey fonts are relatively easy to get up and running with, but do have a rather strange format. A hershey font file (&quot;.jhf&quot;) contains lines like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">501  9I[RFJ[ RRFZ[ RMTWT</span></div><div class="" style="color:#404040"><span class="">  502 24G\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[</span></div><div class="" style="color:#404040"><span class="">  503 19H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each line (ignoring line wrapping) defines a symbol, including the symbol number, number of points, left and right boundaries of the symbol, and finally the individual coordinates.</p>
<img class="mx-auto" src="/images/vector-text/hershey-format.svg" alt="" width="709" height="382"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can think of these coordinates as being given in a coordinate system where the center is given by <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mtext mathvariant="monospace">R</mtext><mo separator="true">,</mo><mtext mathvariant="monospace">R</mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(\texttt{R}, \texttt{R})</annotation></semantics></math></span></span>, with each row below or column to the right getting the next letter, and each row above or column to the left getting the previous letter.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The line above describes symbol <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">501</code>, which is made up of nine total points and extends from column <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext mathvariant="monospace">I</mtext></mrow><annotation encoding="application/x-tex">\texttt{I}</annotation></semantics></math></span></span> to column <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext mathvariant="monospace">[</mtext></mrow><annotation encoding="application/x-tex">\texttt{[}</annotation></semantics></math></span></span>. The symbol consists of three lines, separated by &quot;pen up&quot; commands.</p>
<img class="mx-auto" src="/images/vector-text/hershey-grid.svg" alt="" width="626" height="780"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I find it easiest to envision these symbols as being plotted on a grid of letters. Usually, however, you will treat each &quot;letter&quot; as an ASCII offset from the letter <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mtext mathvariant="monospace">R</mtext></mrow><annotation encoding="application/x-tex">\texttt{R}</annotation></semantics></math></span></span>, like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">&#x27;I&#x27;</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#1bb3ff">&#x27;R&#x27;</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class="" style="color:#8B5CF6">9</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#1bb3ff">&#x27;[&#x27;</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#1bb3ff">&#x27;R&#x27;</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">9</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As you can see in this example, the lines are not placed in the best order for efficient galvo scanning. This is one of the major drawbacks of Hershey fonts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The second piece of the puzzle is font mapping files. Hershey fonts give each symbol a numeric identifier, but this is unique to the Hershey data -- a separate mapping file is required to convert it to ASCII.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="borland-graphics-interface">Borland Graphics Interface</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A variety of vector fonts were shipped with the Borland Graphics Interface, which then made their way into <a href="https://github.com/gandrewstone/GameMaker/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GameMaker</a>, <a href="https://github.com/apsteinmetz/turboPascal" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Turbo Pascal</a>, and other frameworks. Via those frameworks, this small set of fonts made its way into a large volume of software. One famous example is the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">LITT.CHR</code> font, which became the default font in EAGLE, a popular PCB design program. This was actually how I discovered BGI fonts -- through reading <a href="https://design.astridbin.com/project/little-character" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Astrid&#x27;s excellent post about recreating the LITT.CHR font for modern software</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Borland developed a variety of software development tools, including the aforementioned Turbo Pascal (launched in 1983) and the similarly-named Turbo C (in 1987). Both were compilers intended to run on CP/M and, later, MS-DOS. The Borland Graphics Interface was developed to provide a convenient graphics library for software written using Borland&#x27;s compilers. It was notable for supporting a wide range of graphics adapters.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I was able to find the source files for each of the Borland fonts through the (now MIT-licensed) GameMaker source release, I struggled to find documentation for the font format. The best resource I found was <a href="https://www.fileformat.info/format/borland-chr/corion.htm" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this page on FileFormat.info</a>, which seemed to be mostly accurate, but there was still plenty of trial and error in writing a parser.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.CHR</code> format has some interesting quirks that definitely reflect the time period it was created in. For instance, to save space, each coordinate pair is written using only 2 bytes by encoding each value as a 7-bit twos complement integer, then using the highest bit of each value to carry additional information:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">The individual character definitions consist of a variable number of words</span></div><div class="" style="color:#404040"><span class="">describing the operations required to render a character. Each word</span></div><div class="" style="color:#404040"><span class="">consists of an (x,y) coordinate pair and a two-bit opcode, encoded as shown</span></div><div class="" style="color:#404040"><span class="">here:</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">Byte 1          7   6   5   4   3   2   1   0     bit #</span></div><div class="" style="color:#404040"><span class="">			   op1  &lt;seven bit signed X coord&gt;</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">Byte 2          7   6   5   4   3   2   1   0     bit #</span></div><div class="" style="color:#404040"><span class="">			   op2  &lt;seven bit signed Y coord&gt;</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">		  Opcodes</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">		op1=0  op2=0  End of character definition.</span></div><div class="" style="color:#404040"><span class="">		op1=0  op2=1  Do scan</span></div><div class="" style="color:#404040"><span class="">		op1=1  op2=0  Move the pointer to (x,y)</span></div><div class="" style="color:#404040"><span class="">		op1=1  op2=1  Draw from current pointer to (x,y)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With a bit of bit manipulation, we can transform this format into a series of points for each glyph. A separate table in the font file stores the width of each character.</p>
<img class="mx-auto" src="/images/vector-text/borland-format.svg" alt="" width="776" height="510"/>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="newstroke">NewStroke</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of the most recent additions to the vector font family is <a href="https://vovanium.ru/sledy/newstroke/en" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NewStroke</a>, a font designed for KiCAD. Interestingly, instead of being designed in a more common type of CAD software, NewStroke actually seems to have been designed inside the KiCAD Footprint Editor. I am not sure why this was done.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since each character is a footprint, pin definitions are used to specify metrics such as the left and right boundary of the character.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2264" height="1664" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fvector-text%2Fnewstroke_footprint.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fvector-text%2Fnewstroke_footprint.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The glyphs are are defined in a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.lib</code> file (not the modern <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.kicad_sym</code> format), where each glyph looks like this:</p>
<img class="mx-auto" src="/images/vector-text/newstroke-format.svg" alt="" width="794" height="452"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thankfully, there are not that many directives to handle! Page 8 of the <a href="https://dev-docs.kicad.org/en/file-formats/legacy-4-to-6/legacy_file_format_documentation.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">KiCAD Legacy File Format Documentation</a> gives detail for each one. We can write a pretty simple parser for this format.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, we still need something to map these symbol names to Unicode codepoints. We can do this using the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">charlist.txt</code> file that is provided alongside the font library:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class=""># symbol list</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">startchar 32</span></div><div class="" style="color:#404040"><span class="">font newstroke_font</span></div><div class="" style="color:#404040"><span class="">// BASIC LATIN (0020-007F)</span></div><div class="" style="color:#404040"><span class="">+ SPACE</span></div><div class="" style="color:#404040"><span class="">+ EXCLAM</span></div><div class="" style="color:#404040"><span class="">+ QUOTE</span></div><div class="" style="color:#404040"><span class="">+ HASH</span></div><div class="" style="color:#404040"><span class="">+ S_CAP LINE_V_CAP</span></div><div class="" style="color:#404040"><span class="">+ PERCENT</span></div><div class="" style="color:#404040"><span class="">+ AMPERSAND</span></div><div class="" style="color:#404040"><span class="">+ APOSTROPHE</span></div><div class="" style="color:#404040"><span class="">+ PAREN</span></div><div class="" style="color:#404040"><span class="">+ !PAREN</span></div><div class="" style="color:#404040"><span class="">+ ASTERISK</span></div><div class="" style="color:#404040"><span class="">+ PLUS</span></div><div class="" style="color:#404040"><span class="">+ COMMA</span></div><div class="" style="color:#404040"><span class="">+ MINUS</span></div><div class="" style="color:#404040"><span class="">+ FULL_STOP</span></div><div class="" style="color:#404040"><span class="">+ SLASH</span></div><div class="" style="color:#404040"><span class="">+ DIGIT_0</span></div><div class="" style="color:#404040"><span class="">+ DIGIT_1</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each line corresponds to a single Unicode codepoint. Notably, some directives are used (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">!</code> to invert the parenthesis character, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">+</code> to combine characters, etc) to reduce the amount of symbols required to generate the font. Fancier characters will be composed of multiple glyph parts, anchored together using additional pin definitions contained with each symbol.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The original NewStroke project used an <a href="https://gitlab.com/kicad/code/kicad/-/blob/afd432e687b80f02035e337c2d1d3b9578835211/helpers/tools_to_build_newstroke-font/fontconv.awk" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AWK script</a> to transpile the original font definitions into a C source code file containing Hershey-style glyph definitions. I was able to use this AWK code as a resource when writing my own parser.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rust-build-scripts">Rust Build Scripts</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When writing parsers for these formats, I encountered the problem of figuring out how to efficiently store and retrieve font data from within a program. It became clear early on that the formats these fonts were originally developed in were not ideal for reading in at runtime -- some made looking up a character by index excessively slow, and others made inefficient use of space.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I thought about using macros to generate code based on the font definitions, I worried that the macro syntax would be overly restrictive. For this reason, I decided to use a build script to parse the font files at compile time and create the glyph tables.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Crates in Rust can provide a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">build.rs</code> script which is invoked before the crate is compiled. In my case, I use this to generate files at runtime with font data.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="packaging">Packaging</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of the things I noticed when starting this project was that support for stroke vector font formats seemed limited in existing libraries. I decided early on that I wanted to publish these parsers as a package for others to use in their projects.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to publish separate crates for each format, in order to keep all of the logic in the build scripts separate, then create a &quot;wrapper&quot; crate which provided each of the available fonts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was my first experience publishing a crate to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">crates.io</code>! I would say it went quite smoothly, and the login and publishing flow took me very little time at all.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The library is available on <a href="https://crates.io/crates/vector-text" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">crates.io</a>, give it a try! Feel free to <a href="https://github.com/breqdev/vector-text/issues" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">open an issue</a> if you find any issues or have ideas for improvements.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I have some plans in the works involving a vector display, and I made this project to give myself an easy way to swap out different fonts of different formats for testing. I&#x27;m quite happy with the API I was able to create!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Writing parsers like this is always an interesting challenge that I often don&#x27;t get to take on. I hope this library is useful to anyone else attempting a similar project. I definitely expect aspects of it to evolve as I begin using it more.</p></div>]]></description>
            <link>https://breq.dev/projects/vector-text</link>
            <guid isPermaLink="false">/projects/vector-text</guid>
            <category><![CDATA[graphics]]></category>
            <category><![CDATA[vector]]></category>
            <pubDate>Sat, 14 Mar 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Infinite Coffee Glitch]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-800"><h2 class="font-bold text-2xl mt-4">TL;DR</h2><p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://terminal.shop" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">terminal.shop</a> is an online store selling coffee beans via an SSH-based interface. They also provide an API allowing users to place orders via HTTP requests. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/order</code> endpoint had inadequate validation, allowing for nonsensical orders including empty carts, items with quantity 0, and more interestingly: <strong>the ability to get coffee for free by adding negative quantity items to your cart.</strong> All you had to do is call this endpoint with a quantity of -1 for an item and $-22 would be deducted from your order total.</p><p class="mx-auto my-4 max-w-prose font-body text-lg ">The vulnerability was responsibly disclosed and has been patched. (But even though you can no longer get coffee for free, please still check them out as they sell great products at reasonable prices!)</p></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My girlfriends and I are big coffee enjoyers, and since getting an espresso machine in 2024, we&#x27;ve gotten deep into trying different types of coffee beans in lattes and other drinks. So when I stumbled upon <a href="https://terminal.shop/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">terminal.shop</a> last summer, I figured it would be a fun gimmick and a way to try out some different beans. The process is pretty simple: SSH into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">terminal.shop</code> and use your keyboard to navigate the menus and order yourself some coffee.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1364" height="1012" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Fstorefront.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fterminal%2Fstorefront.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fterminal%2Fstorefront.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Running a storefront over SSH works really well! Pages load almost instantly (since they&#x27;re just an 80x24 grid of characters). While you don&#x27;t have the full abilities of CSS, they&#x27;ve made extensive use of <a href="https://en.wikipedia.org/wiki/ANSI_escape_code" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ANSI codes</a> to create an aesthetically pleasing interface. Unlike HTTPS, SSH doesn&#x27;t have public key infrastructure -- there&#x27;s no certificate authority asserting that the terminal.shop server is legitimate. However, they put their SSH public key on their website so you can verify it yourself, and once you login the first time, your client will store the fingerprint to ensure the server is legitimate on future logins.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While we placed our first order because buying something over SSH sounded too fun to pass up, we ended up really liking the decaf and dark roast. However, once we become semi-regular customers, ordering via SSH lost its initial novelty, and I wanted to try something new.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="none-coffee-with-left-shipping">None Coffee with Left Shipping</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">terminal.shop provides an <a href="https://www.terminal.shop/api" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">HTTP API</a> that allows you to place orders. They also have a bunch of <a href="https://www.terminal.shop/api#client-sdks" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">client libraries</a>, but I found the bare HTTP documentation easier to read, so I set out to write a Bash script so I could place orders from my terminal without the hassle of a TUI. My goal was to have one command I could run any time we needed to stock up.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, the development backend at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.dev.terminal.shop</code> was down when I tried to test my code, so I sighed, took out my real credit card, crossed my fingers and hit enter. I immediately got a notification that I had been charged $8 and hadn&#x27;t received a confirmation email. After a bit more digging, I realized where I had gone wrong -- I had supplied the <em>product ID</em> of each product I wanted to buy instead of the <em>product variant ID</em>, causing the order to be created with an empty cart, myself to be billed $8 for shipping and $0 for my nonexistent items, and then (presumably) the backend crashing later when trying to generate my order confirmation.</p>
<div class="border-gray-400 border-2 max-w-[min(36rem,100%)] print:max-w-sm overflow-clip rounded-2xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1006" height="834" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Fsupport-email.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fterminal%2Fsupport-email.png&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fterminal%2Fsupport-email.png&amp;w=2048&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Immediately after I realized this, I sent an email to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">support@terminal.shop</code>. I didn&#x27;t get a response and, for a time, resigned myself to the fact that I had paid $8 for an interesting story. (I don&#x27;t blame them for not getting back to me, Terminal Products seems to be a side project of a few content creators and I&#x27;m sure that job can be incredibly hectic.)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="bash-hacking">Bash hacking</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I patched my code and ordered again. This time, it was a success! However, something was up with the packing slip...</p>
<div class="grid grid-cols-1 sm:grid-cols-[1fr,2fr] gap-2 max-w-4xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2800" height="3733" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Fpacking-slip-002.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fterminal%2Fpacking-slip-002.jpg&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1886" height="904" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Forder-002.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fterminal%2Forder-002.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fterminal%2Forder-002.png&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All four items in the shop were present in both the email and the packing slip, but the two I did not order had a quantity set to zero. This was a byproduct of how my ordering script worked: instead of adding each product to the payload when a user ordered it, I always put all four available variant IDs into the payload with a quantity set to zero unless that argument was set to zero.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">#!/bin/bash</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># usage: ./order.sh --segfault 1 --404 1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">OBJECT_OBJECT_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">SEGFAULT_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">DARK_MODE_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">_404_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">while</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">[</span><span class=""> </span><span class="" style="color:#ff218c">$#</span><span class=""> -gt </span><span class="" style="color:#8B5CF6">0</span><span class=""> </span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">do</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">case</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;</span><span class="" style="color:#ff218c">$1</span><span class="" style="color:#1bb3ff">&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">in</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    --object-object</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">OBJECT_OBJECT_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#ff218c">$2</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#404040">shift</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    --segfault</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">SEGFAULT_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#ff218c">$2</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#404040">shift</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    --dark-mode</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">DARK_MODE_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#ff218c">$2</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#404040">shift</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    --404</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">_404_QTY</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#ff218c">$2</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#404040">shift</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""> </span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    *</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#404040">echo</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Unknown option: </span><span class="" style="color:#ff218c">$1</span><span class="" style="color:#1bb3ff">&quot;</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#404040">exit</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">esac</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">done</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">ORDER_PAYLOAD</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#ff218c">$(</span><span class="" style="color:#ff218c">jq -n </span><span class="" style="color:#1bb3ff">&#x27;{</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">    addressID: &quot;&#x27;</span><span class="" style="color:#ff218c">$SAVED_ADDRESS_ID</span><span class="" style="color:#1bb3ff">&#x27;&quot;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">    cardID: &quot;&#x27;</span><span class="" style="color:#ff218c">$SAVED_CARD_ID</span><span class="" style="color:#1bb3ff">&#x27;&quot;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">    variants: {</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">        &quot;&#x27;</span><span class="" style="color:#ff218c">$ITEM_OBJECT_OBJECT</span><span class="" style="color:#1bb3ff">&#x27;&quot;: &#x27;</span><span class="" style="color:#ff218c">$OBJECT_OBJECT_QTY</span><span class="" style="color:#1bb3ff">&#x27;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">        &quot;&#x27;</span><span class="" style="color:#ff218c">$ITEM_SEGFAULT</span><span class="" style="color:#1bb3ff">&#x27;&quot;: &#x27;</span><span class="" style="color:#ff218c">$SEGFAULT_QTY</span><span class="" style="color:#1bb3ff">&#x27;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">        &quot;&#x27;</span><span class="" style="color:#ff218c">$ITEM_DARK_MODE</span><span class="" style="color:#1bb3ff">&#x27;&quot;: &#x27;</span><span class="" style="color:#ff218c">$DARK_MODE_QTY</span><span class="" style="color:#1bb3ff">&#x27;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">        &quot;&#x27;</span><span class="" style="color:#ff218c">$ITEM_404</span><span class="" style="color:#1bb3ff">&#x27;&quot;: &#x27;</span><span class="" style="color:#ff218c">$_404_QTY</span><span class="" style="color:#1bb3ff">&#x27;,</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">    }</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff">}&#x27;</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a purely arbitrary choice I made since I thought the ergonomics of doing it this way was easier in Bash versus trying to conditionally add things to the JSON payload. I figured that the backend would just filter out any items with a quantity of zero before the order processed. But it seemed like those &quot;quantity zero&quot; items were still there!</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="just-how-much-does-this-endpoint-allow">Just how much does this endpoint allow?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An idea dawned on us. It seemed like this endpoint had pretty poor validation overall. If we placed an order for the 2 types of coffee we wanted, then ordered -1 of a coffee we didn&#x27;t care about, maybe we could receive these items for less money and thus get back the $8 we lost earlier!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We were almost certain that it wouldn&#x27;t work, but I ran the script again:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">./order.sh --404 </span><span class="" style="color:#8B5CF6">1</span><span class=""> --dark-mode </span><span class="" style="color:#8B5CF6">1</span><span class=""> --segfault -1</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Verified the payload it created:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;addressID&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;shp_XXXXXXXXXXXXXXXXXXXXXXXXXX&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;cardID&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;crd_XXXXXXXXXXXXXXXXXXXXXXXXXX&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;variants&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&quot;var_01J1JFE53306NT180RC4HGPWH8&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&quot;var_01J1JFDMNBXB5GJCQF6C3AEBCQ&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">-1</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&quot;var_01J1JFF4D5PBGT0W2RJ7FREHRR&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&quot;var_01J1JFEP8WXK5MKXNBTR2FJ1YC&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then sent it off, and watched the response come in:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">&quot;data&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;ord_XXXXXXXXXXXXXXXXXXXXXXXXXX&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We successfully created an order! Just like that, we got a notification that the card had been charged $30! Then, checking my email, I saw the order confirmation also listed payment of $30: 1 bag of 404 at $22, 1 bag of Dark Mode at $22, 0 bags of [object Object] at $0, and -1 bags of Segfault for $-22.</p>
<div class="border-gray-400 border-2 max-w-[min(48rem,100%)] print:max-w-sm overflow-clip rounded-2xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1894" height="900" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Forder-003.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fterminal%2Forder-003.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fterminal%2Forder-003.png&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We waited patiently for the tracking number. Once it arrived, we checked the metadata, and the shipping label listed the package as weighing 12 oz -- the weight of one bag. It would seem, somehow, our order got corrected and we were receiving only the one bag we had paid for.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Until... a box showed up outside our apartment! I picked it up and noticed it weighed far more than a single bag. Once I opened it, I realized we had been sent all four types of coffee!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="4096" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Fpacking-slip-003.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fterminal%2Fpacking-slip-003.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It appears the order was able to make it all the way to the human fulfilling it without being filtered out, who then probably saw the packing list, thought &quot;oh, I guess the quantity column is messed up on this one,&quot; and packed and shipped it off.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It quickly dawned on us what we had actually done: paid $30 for $88 worth of coffee and put Terminal Products in the position of shipping a 48 oz package with a label that said 12 oz. Not ideal!</p>
<div class="border-gray-400 border-2 max-w-[min(48rem,100%)] print:max-w-sm overflow-clip rounded-2xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="962" height="670" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fterminal%2Fdiscord.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fterminal%2Fdiscord.png&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fterminal%2Fdiscord.png&amp;w=2048&amp;q=75"/></div></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="aftermath">Aftermath</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As it turns out, my girlfriend Ava realized that she unexpectedly shares a mutual acquaintance with some of the terminal.shop team, so word reached them pretty quickly.</p>
<div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thankfully, the folks at Terminal were super chill about this and quickly patched the issue. Not only do I get to keep four times as much coffee as I expected, but we were given a reward for reporting this in the form of even more coffee. (Friends in Boston, please hit me up if you want a latte sometime!)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">From what I can tell, everything exploitable via bare HTTP requests would be similar to accomplish with the official client libraries. The only &quot;trick&quot; was using the API, calling the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/order</code> endpoint directly instead of adding items through <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/cart/item</code>, and just putting weird stuff in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">variants</code> field.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It was fun to watch the hypotheses come in once this was announced online: was it an obscure SSH feature? Something with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SendEnv</code>? Terminal control characters?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These explanations were all unlikely. You might think terminal.shop is built on a traditional server like OpenSSH, with the login shell set to a TUI program, which would thus leave it open to bugs or misconfigurations in a wide range of obscure SSH features. However, the answer is much nicer -- the terminal.shop TUI is built with <a href="https://github.com/charmbracelet/wish" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Wish</a>, a framework by <a href="https://charm.land/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Charm</a> that allows you to create apps accessible over SSH without ever creating an actual shell. It&#x27;s the same framework I used for <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/fissh">fissh.breq.dev</a>, a tiny app I made about a year ago with Ava that presents you with an ASCII drawing of a fish every day at 11:11.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Discovering this vulnerability was a long adventure in the making! I want to thank my girlfriends <a href="https://avasilver.dev" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a> and <a href="https://miakizz.quest" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a> for encouraging me and offering advice, <a href="https://aaronstuyvenberg.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AJ Stuyvenberg</a> for getting us connected to the team at Terminal, and of course all the folks at Terminal Products for having an open and positive attitude towards security research. Sometimes, the most powerful bugs are the ones that require the least complicated exploits!</p></div>]]></description>
            <link>https://breq.dev/2026/01/14/infinite-coffee-glitch</link>
            <guid isPermaLink="false">/2026/01/14/infinite-coffee-glitch</guid>
            <category><![CDATA[hacking]]></category>
            <category><![CDATA[coffee]]></category>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Parsing historical MBTA data]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="transit-and-data">Transit and Data</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Transit systems and public data are a great match. In my daily life, I interact with so many devices and applications which pull from transit data, from the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/matrix2">LED matrix in my living room</a>, to the app on my phone, and to the countdown clocks within the station itself. It&#x27;s also incredibly accessible, too, as the myriad of DIY projects pulling in transit data demonstrates.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are, in general, two types of data about a transit system: what the system is in <em>theory</em> (schedules, routes, and stations determined months in advance), and what the system is in <em>practice</em> (vehicle locations, arrival predictions, and dropped/added trips updated in realtime).</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="transit-systems-in-theory">Transit Systems in Theory</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Describing what a transit system does in theory is the easy part. The <a href="https://gtfs.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">General Transit Feed Specification</a> defines a common format made up (in true 2006 fashion) of a set of TXT files contained within a ZIP file, each describing a different aspect of the system. For instance, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stops.txt</code> describes the vehicle stops and stations in the system, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_times.txt</code> describes when each stop is serviced by a vehicle trip, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">trips.txt</code> can specify the train number, whether bikes are aloud, and more.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The benefit of this approach is clear: sharing a common standard means that code written for one city can work seamlessly with others. Smaller transit operators can create these files by hand, and larger ones can build up the necessary automation to handle hundreds of routes and thousands of stops. Since these ZIP files usually only change when new schedules are determined, distributing them is straightforward and storing historical ones is easy to do.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="transit-systems-in-practice">Transit Systems in Practice</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Realtime data is where things get messy. The requirements are more demanding. Data usually needs to be generated and distributed without a human in the loop, and clients need to pull updates every minute or faster.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">GTFS does offer a solution to this in the form of <a href="https://gtfs.org/documentation/realtime/reference/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GTFS-Realtime</a>. However, being a Google initiative, they chose to build this using <a href="https://protobuf.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Protobuf</a> as an interchange format, which requires specific language bindings to work with. My home system, the MBTA, chose to offer a <a href="https://github.com/mbta/gtfs-documentation/blob/master/reference/gtfs-realtime.md#json-feeds" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">JSON version</a> of their feeds as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Even still, I tend to use their excellent, well-documented <a href="https://api-v3.mbta.com/docs/swagger/index.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">service API</a> which makes it easier to ingest only the data relevant to the lines and stations I need. Interoperability with other systems is usually not a priority for my projects.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">GTFS-Realtime, however, does not specify how <em>historical</em> data should be represented and stored, leaving transit systems to invent bespoke formats for this data -- that is, if they choose to make it available at all.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="historical-data-at-the-mbta">Historical Data at the MBTA</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="lamp">LAMP</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The MBTA has a service called <a href="https://performancedata.mbta.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">LAMP</a> (Lightweight Application for Measuring Performance), which does three things:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">Publish historical GTFS schedule data, showing the state of the system &quot;in theory&quot; for any arbitrary date since 2009.</li>
<li class="my-2 pl-2">Publish historical subway performance data (the system &quot;in practice&quot;) sorted by service date.</li>
<li class="my-2 pl-2">Publish miscellaneous datasets for MBTA-internal uses (while these are public, they are entirely undocumented).</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That second point is what we&#x27;ll focus on for parsing historical realtime data.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="open-data-portal">Open Data Portal</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <a href="https://mbta-massdot.opendata.arcgis.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MBTA Open Data Portal</a> contains lots of additional reports generated by the MBTA covering ridership, predictions accuracy, and more across the various transit modes. One such dataset is the <a href="https://mbta-massdot.opendata.arcgis.com/datasets/924df13d845f4907bb6a6c3ed380d57a/about" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Bus Arrival Departure Times 2025</a> dataset, which nicely complements the subway data published by the LAMP team.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="lets-get-parsing">Let&#x27;s get parsing</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="subway-data">Subway data</h3>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="data-format">Data format</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The subway data we&#x27;re interested in is distributed in the Parquet format, using URLs like this for each day of service:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">https://performancedata.mbta.com/lamp/subway-on-time-performance-v1/YYYY-MM-DD-subway-on-time-performance-v1.parquet</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <a href="https://parquet.apache.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Parquet file format</a> is an Apache project specification for storing tabular data efficiently. They pack the column data efficiently but don&#x27;t require the receiver to know the schema definition like Protobuf does, which means we can easily throw these files into <a href="https://pandas.pydata.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Pandas</a>, the popular Python data analysis library.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> pandas </span><span class="" style="color:#8B5CF6">as</span><span class=""> pd</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">path </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;https://performancedata.mbta.com/lamp/subway-on-time-performance-v1/2025-10-31-subway-on-time-performance-v1.parquet&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">df </span><span class="" style="color:#8B5CF6">=</span><span class=""> pd</span><span class="" style="color:#ff218c">.</span><span class="">read_parquet</span><span class="" style="color:#ff218c">(</span><span class="">path</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">with</span><span class=""> </span><span class="" style="color:#1bb3ff">open</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;data.json&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;w&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> out</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    out</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">.</span><span class="">to_json</span><span class="" style="color:#ff218c">(</span><span class="">orient</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#1bb3ff">&quot;records&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can see that the data has 27 columns. While these aren&#x27;t documented anywhere, here&#x27;s how I assume the data is structured:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Each entry describes a vehicle arriving and/or departing a station as part of a revenue trip.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_id</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">parent_station</code> describe which platform and station the vehicle was at: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_id</code> identifies the platform (for instance, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">70513</code> means the northbound platform at <em>East Somerville</em>), and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">parent_station</code> describes the station it belongs to (in this case, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">place-esomr</code>).</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">move_timestamp</code> seems to be when the train starting moving <em>towards</em> the given station, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_timestamp</code> is when it reached the station.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">travel_time_seconds</code> seems to be the amount of time it took the train to reach the given station, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dwell_time_seconds</code> is how long it spent there.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">service_date</code> describes the service date as an integer with a decimal expansion of the form YYYYMMDD... bruh</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">route_id</code> defines the specific route, such as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Blue</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Green-E</code>, or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Red</code>. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">branch_route_id</code> defines the branch, such as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Blue</code> (no branching), <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Green-E</code>, or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Red-A</code>. I am unsure why the Red line branching is treated differently than the Green line here.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">direction_id</code> is either <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">true</code> or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">false</code>, depending on which way the train is heading. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">direction</code> is the human-readable name, like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">South</code>. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">direction_destination</code> is the direction given as a destination station or station pair, like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Ashmont/Braintree</code> or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Boston College</code>.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">start_time</code> seems to be the time that the vehicle started moving, given as &quot;seconds since midnight at the start of the service day&quot;. Since the MBTA defines &quot;service days&quot; as starting and ending around 3 AM, the first vehicles have a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">start_time</code> of around <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">17680</code> (4:54 AM) and the last ones have a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">start_time</code> of around <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">95976</code> (2:39 AM). <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_count</code> is the number of stops that vehicle has made since that time, I guess?</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">vehicle_id</code> is a unique identifier for the vehicle, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">vehicle_label</code> is a human-readable label (usually the number of the first one or two cars), and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">vehicle_consist</code> is the car numbers of each car in the train.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">trip_id</code> identifies the trip that the vehicle was on.</li>
</ul></div>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="simulating-trips">Simulating trips</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Suppose I had been on the southbound platform at Sullivan Station at 5:15 PM. When would I have made it to North Station?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s start by finding all the trips which arrived at North Station coming southbound.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">trips_to_target </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;parent_station&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;place-north&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;direction&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;South&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">]</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Parent Station</th><th class="border border-black p-2 dark:border-white">Move Timestamp</th><th class="border border-black p-2 dark:border-white">Stop Timestamp</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction</th><th class="border border-black p-2 dark:border-white">Trip ID</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">70026</td><td class="border border-gray-500 p-2">place-north</td><td class="border border-gray-500 p-2">1761902928.0</td><td class="border border-gray-500 p-2">1761903013.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525780</td></tr><tr><td class="border border-gray-500 p-2">70026</td><td class="border border-gray-500 p-2">place-north</td><td class="border border-gray-500 p-2">1761903114.0</td><td class="border border-gray-500 p-2">1761903199.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525786</td></tr><tr><td class="border border-gray-500 p-2">70026</td><td class="border border-gray-500 p-2">place-north</td><td class="border border-gray-500 p-2">1761903280.0</td><td class="border border-gray-500 p-2">1761903367.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525792</td></tr><tr><td class="border border-gray-500 p-2">70026</td><td class="border border-gray-500 p-2">place-north</td><td class="border border-gray-500 p-2">1761903673.0</td><td class="border border-gray-500 p-2">1761903759.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525798</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, let&#x27;s find the earliest one of those trips which also stopped at Sullivan.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">trip_ids </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_to_target</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">unique</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">trips_from_start </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;parent_station&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;place-sull&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;direction&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;South&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">isin</span><span class="" style="color:#ff218c">(</span><span class="">trip_ids</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">]</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Parent Station</th><th class="border border-black p-2 dark:border-white">Move Timestamp</th><th class="border border-black p-2 dark:border-white">Stop Timestamp</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction</th><th class="border border-black p-2 dark:border-white">Trip ID</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761902684.0</td><td class="border border-gray-500 p-2">1761902737.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525780</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761902876.0</td><td class="border border-gray-500 p-2">1761902929.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525786</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761903061.0</td><td class="border border-gray-500 p-2">1761903110.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525792</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761903454.0</td><td class="border border-gray-500 p-2">1761903502.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70525798</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, most of these are the same trip IDs, but now the timestamps match up with the train&#x27;s stop at Sullivan. If we were dealing with different branches of the Green Line, for instance, this step would also filter out trips which don&#x27;t run between our station pair.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, let&#x27;s find the first trip from the start which departed after we got to the station.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Times in this data are represented as Unix timestamps (i.e., seconds since 1970), seemingly in the local timezone (U.S. Eastern time). So, for instance, the first trip in our list arrived at the station at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">1761902737</code> seconds, or at 5:25:37 AM on 2025-10-31.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> datetime</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;2025-10-31 17:15:00&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="">timestamp</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">trips_after_time </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_from_start</span><span class="" style="color:#ff218c">[</span><span class="">trips_from_start</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;stop_timestamp&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> timestamp</span><span class="" style="color:#ff218c">]</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Parent Station</th><th class="border border-black p-2 dark:border-white">Move Timestamp</th><th class="border border-black p-2 dark:border-white">Stop Timestamp</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction</th><th class="border border-black p-2 dark:border-white">Trip ID</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761946007.0</td><td class="border border-gray-500 p-2">1761946055.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70526030</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761946114.0</td><td class="border border-gray-500 p-2">1761946163.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70526038</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761946438.0</td><td class="border border-gray-500 p-2">1761946487.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70526046</td></tr><tr><td class="border border-gray-500 p-2">70030</td><td class="border border-gray-500 p-2">place-sull</td><td class="border border-gray-500 p-2">1761946646.0</td><td class="border border-gray-500 p-2">1761946693.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70526054</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first of those trips is trip ID <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">70526030</code>, which arrived at Sullivan at 5:27:35 PM.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">next_train </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_after_time</span><span class="" style="color:#ff218c">.</span><span class="">loc</span><span class="" style="color:#ff218c">[</span><span class="">trips_after_time</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;stop_timestamp&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">idxmin</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">train_arrival </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> next_train</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;parent_station&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;place-north&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Looking up its arrival into North Station, we see:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Parent Station</th><th class="border border-black p-2 dark:border-white">Move Timestamp</th><th class="border border-black p-2 dark:border-white">Stop Timestamp</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction</th><th class="border border-black p-2 dark:border-white">Trip ID</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">70026</td><td class="border border-gray-500 p-2">place-north</td><td class="border border-gray-500 p-2">1761946237.0</td><td class="border border-gray-500 p-2">1761946322.0</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">70526030</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It appears we would have arrived at 5:32:02 PM.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="bus-data">Bus data</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The bus data is a little different. The MBTA provides it as a ZIP file for each year, with a CSV file for each month. At time of writing, the latest one is <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">MBTA-Bus-Arrival-Departure-Times_2025-09.csv</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The data is pretty straightforward, providing the following columns:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">service_date</code> is self-explanatory.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">route_id</code> is the bus number. Note that for some buses, the internal route ID does not match the consumer-facing bus number. For instance, route <a href="https://www.mbta.com/schedules/8993/line" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">89/93</a> is identified as 194 internally, and the first <a href="https://www.mbta.com/schedules/39/line" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">39</a> bus of the day is considered route 192 internally.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">direction_id</code> is the direction identifier, which appears to only ever be <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Outbound</code> or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Inbound</code>.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">half_trip_id</code> is like the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">trip_id</code> of the subway data, but since the outbound and inbound trip of a bus route often share a trip ID, this disambiguates them.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_id</code> is the identifier of the platform at which the bus stops. Large stations like Sullivan provide many bus platforms, each with its own ID.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">point_type</code> specifies if this stop is a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Startpoint</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Midpoint</code>, or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Endpoint</code> of the route.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">scheduled</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">actual</code> give the scheduled and actual arrival times for the bus, respectively. However, for some reason, the date format is of the form <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">1900-01-01T14:28:00Z</code>... We&#x27;ll dig into this more later.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Notably, not every stop is covered in this data, only the <em>time points</em> (places along the route with a scheduled arrival time). However, the time points are typically placed at high-traffic stops like subway stations or the start and end of the line, so they are probably useful anyway.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="basic-parsing">Basic parsing</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s see if we can repeat a similar &quot;simulated journey&quot; to what we did with the subway, but with the bus data. Suppose I&#x27;m trying to get from Harvard to Hynes Convention Center station using the 1 bus on 2025-09-20 at 11:00 AM.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ingesting the CSV file is quite easy:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> pandas </span><span class="" style="color:#8B5CF6">as</span><span class=""> pd</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">path </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;MBTA_Bus_Arrival_Departure_Times_2025/MBTA-Bus-Arrival-Departure-Times_2025-09.csv&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">df </span><span class="" style="color:#8B5CF6">=</span><span class=""> pd</span><span class="" style="color:#ff218c">.</span><span class="">read_csv</span><span class="" style="color:#ff218c">(</span><span class="">path</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first part is quite similar to simulating a subway trip. Let&#x27;s try selecting all of the trips that arrived at our destination stop ID, stop <a href="https://www.mbta.com/stops/79" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">79</a>, then find the records for those trips departing from Harvard, stop <a href="https://www.mbta.com/stops/110" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">110</a>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">trips_to_target </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;stop_id&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">79</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;direction_id&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Inbound&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">trip_ids </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_to_target</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;half_trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">unique</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">trips_from_start </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;stop_id&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">110</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;direction_id&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Inbound&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;half_trip_id&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">isin</span><span class="" style="color:#ff218c">(</span><span class="">trip_ids</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">]</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Service Date</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction ID</th><th class="border border-black p-2 dark:border-white">Half Trip ID</th><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Scheduled</th><th class="border border-black p-2 dark:border-white">Actual</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">2025-09-01</td><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">68099570</td><td class="border border-gray-500 p-2">110</td><td class="border border-gray-500 p-2">1900-01-01T11:31:00Z</td><td class="border border-gray-500 p-2">1900-01-01T11:44:24Z</td></tr><tr><td class="border border-gray-500 p-2">2025-09-01</td><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">68099572</td><td class="border border-gray-500 p-2">110</td><td class="border border-gray-500 p-2">1900-01-01T12:40:00Z</td><td class="border border-gray-500 p-2">1900-01-01T13:01:02Z</td></tr><tr><td class="border border-gray-500 p-2">2025-09-01</td><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">68099573</td><td class="border border-gray-500 p-2">110</td><td class="border border-gray-500 p-2">1900-01-01T22:06:00Z</td><td class="border border-gray-500 p-2">1900-01-01T22:54:09Z</td></tr><tr><td class="border border-gray-500 p-2">2025-09-01</td><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">68099575</td><td class="border border-gray-500 p-2">110</td><td class="border border-gray-500 p-2">1900-01-01T23:42:00Z</td><td class="border border-gray-500 p-2">1900-01-02T00:18:43Z</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And finally, we need to filter by trips happening after our chosen start time, so we need to handle the problem of dates.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dates-and-times-nonsense">Dates and times nonsense</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">How do we assemble the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">service_date</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">scheduled</code>/<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">actual</code> fields into an actual timestamp like we got with the subway data?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s start by parsing the service date.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;2025-09-01&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2025</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">9</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, let&#x27;s parse the timestamp offset. Note that it is in UTC, not local time.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">offset </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;1900-01-01T11:31:00Z&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1900</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">11</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">31</span><span class="" style="color:#ff218c">,</span><span class=""> tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">timezone</span><span class="" style="color:#ff218c">.</span><span class="">utc</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, the offset is an <em>aware</em> datetime, meaning it contains timezone info. Combining aware datetimes with their counterparts, <em>naive</em> datetimes, is usually not allowed. Let&#x27;s make the service date an aware datetime as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You might be tempted to use the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">astimezone</code> method, but this will convert the naive datetime to an aware datetime assuming the naive datetime is in local time, which is not what we want -- we want to keep the year/month/day values the same, but just attach timezone information to this instance. We can use the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">replace</code> method and replace the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">tzinfo</code> field.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">replace</span><span class="" style="color:#ff218c">(</span><span class="">tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">UTC</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2025</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">9</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">,</span><span class=""> tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">timezone</span><span class="" style="color:#ff218c">.</span><span class="">utc</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cool. Now the only thing we need to do with the offset is subtract the placeholder date (1900-01-01). Python implements this nicely, where subtracting one datetime from another gives a <em>timedelta</em> object.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">offset </span><span class="" style="color:#8B5CF6">-=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1900</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">UTC</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">timedelta</span><span class="" style="color:#ff218c">(</span><span class="">seconds</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">41460</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we can add this to our service date:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">+=</span><span class=""> offset</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2025</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">9</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">11</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">31</span><span class="" style="color:#ff218c">,</span><span class=""> tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">timezone</span><span class="" style="color:#ff218c">.</span><span class="">utc</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And finally, convert it from UTC to our local time, then strip the timezone info to match the behavior of our other code. If this were production code, we would want to only use aware datetimes... but we&#x27;re just messing around so let&#x27;s do what&#x27;s easy.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> zoneinfo</span></div><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">astimezone</span><span class="" style="color:#ff218c">(</span><span class="">zoneinfo</span><span class="" style="color:#ff218c">.</span><span class="">ZoneInfo</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;America/New_York&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">replace</span><span class="" style="color:#ff218c">(</span><span class="">tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">None</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2025</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">9</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">7</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">31</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cool! So that trip was at 2025-09-01 at 7:31 AM.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pulling-it-together">Pulling it together</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, back to the show. We were trying to filter by trips happening after a given time. Let&#x27;s add a column to our dataframe with a proper timestamp to match our other data.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> zoneinfo</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">convert_timestamp</span><span class="" style="color:#ff218c">(</span><span class="">row</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#8B5CF6">not</span><span class=""> </span><span class="" style="color:#1bb3ff">isinstance</span><span class="" style="color:#ff218c">(</span><span class="">row</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;actual&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">str</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> pd</span><span class="" style="color:#ff218c">.</span><span class="">NA</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="">row</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;service_date&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    offset </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="">row</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;actual&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">replace</span><span class="" style="color:#ff218c">(</span><span class="">tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">UTC</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    offset </span><span class="" style="color:#8B5CF6">-=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1900</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">UTC</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    timestamp </span><span class="" style="color:#8B5CF6">+=</span><span class=""> offset</span></div><div class="" style="color:#404040"><span class="">    timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">astimezone</span><span class="" style="color:#ff218c">(</span><span class="">zoneinfo</span><span class="" style="color:#ff218c">.</span><span class="">ZoneInfo</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;America/New_York&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> timestamp</span><span class="" style="color:#ff218c">.</span><span class="">replace</span><span class="" style="color:#ff218c">(</span><span class="">tzinfo</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">None</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> timestamp</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;timestamp&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#1bb3ff">apply</span><span class="" style="color:#ff218c">(</span><span class="">convert_timestamp</span><span class="" style="color:#ff218c">,</span><span class=""> axis</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After rebuilding <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">trips_from_start</code> with this new column, we can select the trips after our chosen time, choose the one that departed earliest, and see when it got to Hynes in a similar way to our subway trip simulator.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">timestamp </span><span class="" style="color:#8B5CF6">=</span><span class=""> datetime</span><span class="" style="color:#ff218c">.</span><span class="">datetime</span><span class="" style="color:#ff218c">.</span><span class="">fromisoformat</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;2025-09-20 11:00:00&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">trips_after_time </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_from_start</span><span class="" style="color:#ff218c">[</span><span class="">trips_from_start</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;timestamp&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> timestamp</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">next_bus </span><span class="" style="color:#8B5CF6">=</span><span class=""> trips_after_time</span><span class="" style="color:#ff218c">.</span><span class="">loc</span><span class="" style="color:#ff218c">[</span><span class="">trips_after_time</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;timestamp&#x27;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">idxmin</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">bus_arrival </span><span class="" style="color:#8B5CF6">=</span><span class=""> df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;half_trip_id&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> next_bus</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;half_trip_id&#x27;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">df</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&#x27;stop_id&#x27;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">79</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">]</span></div></pre></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Service Date</th><th class="border border-black p-2 dark:border-white">Route ID</th><th class="border border-black p-2 dark:border-white">Direction ID</th><th class="border border-black p-2 dark:border-white">Half Trip ID</th><th class="border border-black p-2 dark:border-white">Stop ID</th><th class="border border-black p-2 dark:border-white">Timestamp</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">2025-09-20</td><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">68311684</td><td class="border border-gray-500 p-2">79</td><td class="border border-gray-500 p-2">2025-09-20 11:24:05</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I would&#x27;ve made it to Hynes at 11:24 AM. Not bad!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="simulating-longer-journeys">Simulating longer journeys</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Why do we care about simulating historical journeys, you might ask? It&#x27;s useful for answering a few different types of questions:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">How early should I leave my house to make it to work on time 90% of the time?</li>
<li class="my-2 pl-2">How reliably, on average, can I make certain connections between modes?</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And, my favorite:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">If I wanted to visit every subway station in the MBTA as quickly as possible, what would be the fastest route?</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="simulating-an-existing-speedrun">Simulating an existing speedrun</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Transit system speedrunning is a phenomenon in which competitors attempt to travel through all stations within a system as quickly as possible.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friend <a href="https://tris.fyi" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tris</a> completed a speedrun about a year ago and documented her route in a <a href="https://tacobelllabs.net/@tris/113724679621920495" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mastodon thread</a>. Let&#x27;s see if we can accurately simulate it!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I made a few improvements to the algorithm:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Caching the processed data where possible!</li>
<li class="my-2 pl-2">Instead of using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_timestamp</code> + <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dwell_time_seconds</code>, I used the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">move_timestamp</code> of the station immediately after the given one. This seemed much more accurate for terminus stations where <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dwell_time_seconds</code> was set to null (despite those stations being where the train dwells for the longest amount of time).</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For parts of the run involving walking, I set very optimistic transfer times because I can vouch for the fact that Tris walks very fast.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The code for this is <a href="https://github.com/breqdev/mbta-simulate" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">on GitHub</a> if you want to play around with it.</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Station</th><th class="border border-black p-2 dark:border-white">Actual Time</th><th class="border border-black p-2 dark:border-white">Route</th><th class="border border-black p-2 dark:border-white">Direction</th><th class="border border-black p-2 dark:border-white">Simulated Time</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">Riverside</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113724740717277822" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">07:15</a></td><td class="border border-gray-500 p-2">Green-D</td><td class="border border-gray-500 p-2">East</td><td class="border border-gray-500 p-2">07:15</td></tr><tr><td class="border border-gray-500 p-2">Kenmore</td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2">Green-B</td><td class="border border-gray-500 p-2">West</td><td class="border border-gray-500 p-2">07:48</td></tr><tr><td class="border border-gray-500 p-2">Boston College</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113724981377517592" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">08:18</a></td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">08:15</td></tr><tr><td class="border border-gray-500 p-2">Cleveland Circle</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725072297288138" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">08:41</a></td><td class="border border-gray-500 p-2">Green-C</td><td class="border border-gray-500 p-2">East</td><td class="border border-gray-500 p-2">08:25</td></tr><tr><td class="border border-gray-500 p-2">Park St</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725192574260269" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">09:12</a></td><td class="border border-gray-500 p-2">Green-E</td><td class="border border-gray-500 p-2">West</td><td class="border border-gray-500 p-2">09:07</td></tr><tr><td class="border border-gray-500 p-2">Heath St</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725324958185355" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">09:45</a></td><td class="border border-gray-500 p-2">39</td><td class="border border-gray-500 p-2">Outbound</td><td class="border border-gray-500 p-2">09:42</td></tr><tr><td class="border border-gray-500 p-2">Forest Hills</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725376738178438" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">09:58</a></td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">North</td><td class="border border-gray-500 p-2">09:54</td></tr><tr><td class="border border-gray-500 p-2">Downtown Crossing</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725464921047879" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">10:21</a></td><td class="border border-gray-500 p-2">Red</td><td class="border border-gray-500 p-2">North</td><td class="border border-gray-500 p-2">10:17</td></tr><tr><td class="border border-gray-500 p-2">Alewife</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725578546237980" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">10:50</a></td><td class="border border-gray-500 p-2">Red</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">10:43</td></tr><tr><td class="border border-gray-500 p-2">Davis</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725596752822002" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">10:54</a></td><td class="border border-gray-500 p-2">96</td><td class="border border-gray-500 p-2">Outbound</td><td class="border border-gray-500 p-2">10:46</td></tr><tr><td class="border border-gray-500 p-2">Medford/Tufts</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725633977529438" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">11:04</a></td><td class="border border-gray-500 p-2">Green-E</td><td class="border border-gray-500 p-2">West</td><td class="border border-gray-500 p-2">11:07</td></tr><tr><td class="border border-gray-500 p-2">East Somerville</td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">11:16</td></tr><tr><td class="border border-gray-500 p-2">Union Sq</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725713807971548" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">11:24</a></td><td class="border border-gray-500 p-2">Green-D</td><td class="border border-gray-500 p-2">West</td><td class="border border-gray-500 p-2">11:21</td></tr><tr><td class="border border-gray-500 p-2">North Sta</td><td class="border border-gray-500 p-2">11:36</td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">North</td><td class="border border-gray-500 p-2">11:36</td></tr><tr><td class="border border-gray-500 p-2">Oak Grove</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725875079532776" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">12:05</a></td><td class="border border-gray-500 p-2">Orange</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">12:03</td></tr><tr><td class="border border-gray-500 p-2">Haymarket</td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">12:23</td></tr><tr><td class="border border-gray-500 p-2">Bowdoin</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113725990981012681" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">12:35</a></td><td class="border border-gray-500 p-2">Blue</td><td class="border border-gray-500 p-2">North</td><td class="border border-gray-500 p-2">12:28</td></tr><tr><td class="border border-gray-500 p-2">Wonderland</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726079382870810" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">12:57</a></td><td class="border border-gray-500 p-2">Blue</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">12:50</td></tr><tr><td class="border border-gray-500 p-2">State</td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">WALK</td><td class="border border-gray-500 p-2">13:08</td></tr><tr><td class="border border-gray-500 p-2">Downtown Crossing</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726156172913175" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">13:17</a></td><td class="border border-gray-500 p-2">Red-A</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">13:13</td></tr><tr><td class="border border-gray-500 p-2">Ashmont</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726251169030970" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">13:41</a></td><td class="border border-gray-500 p-2">Mattapan</td><td class="border border-gray-500 p-2">Outbound</td><td class="border border-gray-500 p-2">13:38</td></tr><tr><td class="border border-gray-500 p-2">Mattapan</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726315814984961" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">13:57</a></td><td class="border border-gray-500 p-2">Mattapan</td><td class="border border-gray-500 p-2">Inbound</td><td class="border border-gray-500 p-2">13:51</td></tr><tr><td class="border border-gray-500 p-2">Ashmont</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726356806389810" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">14:08</a></td><td class="border border-gray-500 p-2">Red-A</td><td class="border border-gray-500 p-2">North</td><td class="border border-gray-500 p-2">14:05</td></tr><tr><td class="border border-gray-500 p-2">JFK/UMass</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726417955542565" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">14:23</a></td><td class="border border-gray-500 p-2">Red-B</td><td class="border border-gray-500 p-2">South</td><td class="border border-gray-500 p-2">14:20</td></tr><tr><td class="border border-gray-500 p-2">Braintree</td><td class="border border-gray-500 p-2"><a href="https://tacobelllabs.net/@tris/113726512385880967" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">14:47</a></td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2"></td><td class="border border-gray-500 p-2">14:45</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Not too bad! Especially considering that the &quot;actual time&quot; column is based on Mastodon post timestamps so probably 1-2 minutes behind the actual timing.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="generating-the-optimal-route">Generating the optimal route</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My eventual plan with this is to use it to learn more about what makes an ideal MBTA speedrun, including the route, timing, etc. It&#x27;s been a bucket list item of mine for a while to do a speedrun, and I want to see if I can find something new in terms of route, timing, etc. I think it&#x27;s unlikely I&#x27;ll find anything that substantial that could make a difference, but maybe it&#x27;s possible...</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since running this type of simulation becomes extremely fast, there are a lot of places you could take this other than just tracing manually entered routes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Taking a graph representation of the MBTA system, running a search algorithm to identify a large set of possible routes, and running simulations on each to estimate real-world timing</li>
<li class="my-2 pl-2">Simulating a route at various times of day, days of the week, or even months of the year to find the best time to start a run</li>
<li class="my-2 pl-2">Implementing logic to simulate making decisions at each step of the route based on upcoming train times (e.g., whether an Ashmont or Braintree train arrives first), to evaluate when to make those decisions</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="takeaways">Takeaways</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I hadn&#x27;t really done an in-depth data analysis project quite like this before. I definitely learned a ton!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ingesting data into a data structure like this is something I usually consider &quot;grunt work,&quot; but working on this showed me how wrong that assumption can be. I tried to throw a lot of this work to an LLM, only to watch it struggle against the date and timezone issues and completely miss the nuance between <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stop_timestamp</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">move_timestamp</code>. While I got much further than it did (thankfully for the sake of my job security), it still required me to step away from the problem for a day before I could nail the accuracy.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had also heard people constantly talk about how timezones are hard and date parsing is a mess, but had been spared the brunt of that struggle until now. I have discovered that there are a lot of bad ways to represent dates and times in software. I feel lucky that I do not need to deal with things like this more often.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The last thing I&#x27;ll mention is that this project highlighted the difference between working with data in a domain where a dominant format exists (i.e., realtime GTFS), and working in a domain where implementors have no common format to use (i.e., historical transit data). I find it easy to get annoyed at format specifications not perfectly matching what I want to do... but when standardized formats work, they&#x27;re pretty great!</p></div>]]></description>
            <link>https://breq.dev/2025/11/02/historical-mbta</link>
            <guid isPermaLink="false">/2025/11/02/historical-mbta</guid>
            <category><![CDATA[transit]]></category>
            <category><![CDATA[data]]></category>
            <pubDate>Sun, 02 Nov 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Wall Matrix 3]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3059" height="3053" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-weather.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-weather.jpg&amp;w=3840&amp;q=75"/></div> <div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-transit.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-transit.jpg&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve built several projects based on LED matrices: my first one in <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">2021</a>, and a redesign earlier in <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/matrix2">2025</a> with a larger panel and slimmer enclosure. I&#x27;ve also gotten to spend lots of time fine-tuning the display UI as I learn more about icon design, balancing readability and &quot;glanceability&quot; with information density, and dealing with the unique constraints of a display with a 3mm pixel pitch.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While the hardware was a dramatic step up from my first iteration, I still thought there was room for improvement: more robust mounting of the encoder and the DC power jack, a slimmer construction that could fit closer to the wall, and simpler internal wiring. The obvious next step was a custom PCB. This project came at a time when I was hoping to get back into PCB design, so this seemed like the perfect opportunity!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At the same time, I had relatively recently started full-time work and wanted to add some decorations to my desk area. Since my previous builds had proven extremely useful at indicating bus times and bike availability, I figured that one on my work desk would be similarly useful! Of course, this one had slightly different requirements, so I ended up building a single board and two very different enclosures for it.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pcb-design">PCB Design</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The schematic of the board is very similar to that of the previous build, with a few changes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The <a href="https://www.adafruit.com/product/3211" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit LED Matrix Bonnet</a> was removed and its level shifting chips were placed directly onto the custom PCB</li>
<li class="my-2 pl-2">An ADC and photoresistor were added to enable automatic brightness dimming (a substantial pain point on the previous version!)</li>
<li class="my-2 pl-2">A USB-C port was added to the side of the board as an alternate power option, since using the DC jack on the bottom would prevent standing the display up on a table</li>
</ul></div>
<div class="bg-white"><img class="mx-auto" src="/images/matrix3/schematic.svg" alt="" width="1123" height="794"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The layout followed pretty naturally. I kept the board at 192mm wide to match either a 64-column 3mm-pitch display or a 32-column 6mm-pitch display, gave it rounded bottom corners to match the style of the previous build, and mounted the encoder at the center on one side.</p>
<img class="mx-auto" src="/images/matrix3/back.svg" alt="" width="1123" height="794"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Shameless plug: The renders in this section were made in <a href="https://kicad-pretty.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">my KiCAD SVG prettifier tool</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I like the enclosed build in my apartment, I wanted to leave the option of doing a build with an exposed PCB. The components are all mounted towards the back, and the routing was mostly done only on the back side, leaving the front part of the board mostly empty on both the silkscreen and copper layers. I thought about putting some artwork or text on the silkscreen but couldn&#x27;t think of a good way to make it look professional -- the usable space on the board is asymmetrical and interrupted by through-hole components.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the end, I decided to put a repeating pattern in the copper layer of the board, then order it with <a href="https://oshpark.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OSHPark</a>&#x27;s <a href="https://docs.oshpark.com/services/afterdark/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">After Dark</a> colorway, which uses transparent soldermask to let the copper design shine through. I wanted an angular style to match the vibes of the rigid exposed pixel grid, but worried that an actual grid pattern could clash or appear misaligned. Thus, I chose a hexagonal pattern.</p>
<img class="mx-auto" src="/images/matrix3/front.svg" alt="" width="1123" height="794"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Building and importing this pattern turned into more of an adventure than I had predicted! The process I found is convoluted and by no means ideal. If this were something I was doing more often I would definitely design an automated tool for it, but as it stands, the best way I know to accomplish this is:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">Make your board in KiCAD.</li>
<li class="my-2 pl-2">Draw a shape in Inkscape representing your edge cuts in KiCAD. Make sure the units match up!</li>
<li class="my-2 pl-2">Under &quot;Fill&quot;, choose a pattern you like and set the scaling appropriately.</li>
<li class="my-2 pl-2">Export your KiCAD board as an SVG so you can use it as a reference. The menu option for this is Plot &gt; SVG, then select F.Cu, then check to have Edge.Cuts plotted on all layers.</li>
<li class="my-2 pl-2">Import your plotted SVG into Inkscape and line it up using your Edge.Cuts layer as a reference.</li>
<li class="my-2 pl-2">Manually draw shapes on top of the pattern to mask off all of the areas you do <em>not</em> want to have your copper pattern. It is easiest to put these in a group. These can be as convoluted or basic as you like -- I mostly sketched rectangles over large components and traces.</li>
<li class="my-2 pl-2">Select all of your mask shapes and your shape with the pattern and open the &quot;Shape Builder&quot; tool. Select the area(s) where you want the pattern to show (i.e., the areas without parts). You should be left with a shape matching your intended board, containing your pattern as a fill.</li>
<li class="my-2 pl-2">Next you need to turn the pattern into a basic SVG path. The easiest way to do this is to export it as a PNG (make sure your DPI is 500ish), then re-import it and use the &quot;Trace Bitmap&quot; feature. Click &quot;Update Preview&quot;, and if it looks good then &quot;Apply.&quot;</li>
<li class="my-2 pl-2">Export as yet another SVG. (Make your F.Cu traces hidden before exporting!)</li>
<li class="my-2 pl-2">In KiCAD, Use File &gt; Import &gt; Graphics, select your layer (in our case F.Cu), then place it and manually line it up with your board!</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At my girlfriend <a href="https://miakizz.quest/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a>&#x27;s suggestion, I added a photoresistor and ADC to the circuit to implement automatic brightness control. I haven&#x27;t gotten around to using this yet, mainly because the build at my work desk does not require it and the enclosure design of the one at home blocks outside light. In the future I think I could add a small hole to the bottom of the design which would let light into the enclosure but not be too visible.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pcb-assembly">PCB Assembly</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmt-level-shifter.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmt-level-shifter.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was my first time doing surface-mount soldering! I kept the components manageable (SOIC chips and 0805 passives). Overall I found the experience much easier than I anticipated. Big thanks to Mia for giving very helpful advice throughout. Thankfully, the board worked on the first try!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="enclosure">Enclosure</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-build-front.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-build-front.jpg&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-build-back.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-build-back.jpg&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the build sitting at my work desk, I wanted to build something that showed off the internals while still appearing sturdy and thoughtfully designed. I went with two small 3D-printed parts on opposite sides of the assembly which attach to both the PCB and the panel, allowing the device to be freestanding on a desk or table.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-bikes.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-bikes.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the one in my apartment, I wanted to continue the theme of building something as unobtrusive as possible. Since the PCB was now the same width of the panel, if I wanted to enclose the PCB fully, the enclosure would also need to be a few millimeters wider than the panel. I took this opportunity to recess the panel into the enclosure, giving the system a much more polished look from the side view.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I am not super happy with the large gaps in the design caused by the imprecision of my 3D printer. I do like that 3D printing can give a brightly colored end result, and the form of the parts seems to lend itself well to a 3D-printed design over something like CNC milling. Perhaps I just need to get access to a better 3D printer.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The knob was given to me by a coworker who happened to be getting rid of it. It fits my encoder shaft perfectly! It is a bit taller than I would like but that is mostly a function of how much the shaft protrudes. In the future I will get a shorter encoder shaft or find a way to mount the encoder farther back. I unfortunately can&#x27;t move the entire PCB back without increasing the thickness of the system because of the height of the 2x8 connector going to the LED panel.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="4096" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fthickness.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fthickness.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One thing I do love about the new design is that it sits dramatically closer to the wall, making it match the thickness of photos and artwork I have. Things have come a long way from the ~50mm thickness of the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">original version</a>!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="software">Software</h2>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2717" height="2727" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-bikes.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-bikes.jpg&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-weather.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Flarge-weather.jpg&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2466" height="2466" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-transit.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix3%2Fsmall-transit.jpg&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The largest changes on the software standpoint were extending the previous version to support multiple display sizes and moving a lot of the hardcoded logic (ZIP codes, etc) into a configuration file so my display at work could show different information to my display at home.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I hacked together a barely-working implementation to test the panel which <a href="https://avasilver.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a> kindly refactored into something much nicer.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I think time has shown that I&#x27;m unlikely to stop building this kind of display anytime soon. They serve a really interesting niche of a device that provides basic information at a glance without being obtrusive, and have a unique and interesting look.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I really enjoyed getting back into PCB design and finally conquering my fears regarding surface-mount work. I hadn&#x27;t actually designed and gotten a board fabricated since I was in high school, more than 5 years ago! It was nice to exercise that skill again. SMT soldering, on the other hand, is something I had never attempted before, being scared off by my general clumsiness and the precision and tools often recommended for that kind of work. While I am very glad I bought flux at the recommendation of a friend, I found it to be smooth sailing even without new tips for my iron. Perhaps I&#x27;ll invest in some better tools in the future.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I loved getting to design this iteration with a focus on making it easy to potentially build more in the future. The board came out quite nice, and while there are definitely some things I would change in the future (mostly relating to making the connectors protrude farther out of the case), I am overall quite happy with the results!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Throughout this process, several few people have told me that they would love to build something similar to this for their home. I&#x27;ve considered a few times trying to scale up production, but the parts cost (mostly the panel itself and the Raspberry Pi) makes the total cost of each unit pretty high. There are a few companies making something similar (<a href="https://tidbyt.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tidbyt</a> and <a href="https://rideontime.nyc/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RideOnTime</a> are the two I&#x27;ve seen), but they both use small panels in a thick enclosure style like I did in the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">first iteration</a> of this project. I&#x27;ve grown to really appreciate the large 64x64 panel size as an option, a flat enclosure, and a design more focused on information density.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I don&#x27;t want to start a business selling these or anything, but I could see myself creating better documentation of what hardware to buy, making the software easier to install and use, and organizing a group buy of boards and parts. Shoot me a message if that sounds interesting to you!</p></div>]]></description>
            <link>https://breq.dev/projects/matrix3</link>
            <guid isPermaLink="false">/projects/matrix3</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Sun, 02 Nov 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[FDM 3D printing for the home]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve owned a 3D printer for about ten years now. I got my first 3D printer, a Monoprice MP Select Mini, when I was in middle school as the 3D printing craze was beginning to take off in &quot;maker&quot; culture. My dad likened it to buying his first computer as a kid. But while the home computer was on the verge of an explosion in popularity, the home 3D printer is still uncommon. The machines have undoubtedly gotten better, but FDM (fused deposition modeling) 3D printers still struggle to find broad appeal beyond the same group of tinkerers who were using them years ago.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Disclaimer: My <a href="https://www.quadratic3d.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">employer</a> works in the field of 3D printing, but that is neither FDM nor for personal use!</p>
</blockquote>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="my-journey-with-fdm">My journey with FDM</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My <a href="https://www.mpselectmini.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MP Select Mini</a> held up okay over quite a few years. The hotend was quite prone to clogs and other issues, so I replaced it with a generic upgrade kit one after printing an adapter I found online. Eventually the mainboard started to give up.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I now own an Ender 3 Pro printer that I impulse-bought at Micro Center a few years ago for $99. It works... okay. I&#x27;ve replaced the bed and upgraded to a metal extruder kit. The Z axis still misbehaves a lot, leading occasionally to layers that are too short (causing nozzle drag). I could try replacing the stepper motor, but I don&#x27;t feel like paying $25 just to try something that might not be the actual fix.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Through my work on the Northeastern Mars Rover Team, I&#x27;ve gotten to work with slightly better printers from Bambu and Prusa. They&#x27;re generally nicer to get working, but they still experience frequent issues and require consistent maintenance. They&#x27;re much closer to a tool you&#x27;d find in a machine shop than an appliance you&#x27;d find in a home.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve printed plenty of parts over the years, some more useful than others. Owning a 3D printer has enabled plenty of projects that would otherwise be impossible without a more substantial workshop, like my <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">LED wall sign</a> and its <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/matrix3">sequel</a>, the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/itx-case">custom mini-ITX case I made</a>, and more. That said, it requires consistent maintenance to keep working, frequent retries slowing down the process, and produces lots of scrap.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I&#x27;m happy to print models for my friends, few take advantage of this -- most people just do not reach for 3D printing when they encounter a problem.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="stl-websites">STL websites</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Broadly speaking, most personal 3D printing use cases can be divided into categories: printing existing STL models from sites like Thingiverse and Printables, and doing CAD work to produce unique parts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The culture of sharing STLs within the hobbyist 3D printing community is huge. It&#x27;s trivially easy to download a model from a website, drag and drop it into your slicer, and export G-Code to an SD card for your printer. However, in my experience, most of the models available on these sites are not particularly useful. The vast majority are either decorative parts, or utilitarian parts like headphone stands, shelving and organization products, and cable management parts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In most cases, commercially bought parts are available at similar cost and with dramatically better build quality, surface finish, and durability. In some cases it is even faster to go to a physical store to buy a product than to run an FDM printer for hours.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I can&#x27;t speak as much to printing artistic models or figurines, since that aspect never particularly appealed to me. Without postprocessing, FDM 3D prints just don&#x27;t look that great.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There is undoubtedly a sweet spot here: items that are popular enough that you&#x27;re likely to find an existing model, but not popular enough to warrant that product being commercially produced. Mounting brackets for specific network switches and similar parts sometimes fall into this category. But the bulk of the time I&#x27;ve searched for such a part, 3D models haven&#x27;t been available at the usual places, and I&#x27;ve had to make it myself.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cad-software">CAD software</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1536" height="1542" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffdm%2Ffloppy-organizer.jpeg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffdm%2Ffloppy-organizer.jpeg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffdm%2Ffloppy-organizer.jpeg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A print I recently designed and made for organizing floppy disks for my <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2025/07/01/mavica">Mavica</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In my experience, without an understanding of <em>some</em> form of CAD software, owning a 3D printer is of limited utility. Contrast this with the personal computer: most people who use computers on a daily basis do not know how to program! The typical user can meet most or all of their needs by downloading and utilizing existing software.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was lucky to learn CAD software in middle school as part of &quot;Technology and Engineering&quot; class, in the golden age where SketchUp was the teaching software of choice instead of TinkerCAD. SketchUp undoubtedly has limitations (I still don&#x27;t know how to <a href="https://forums.sketchup.com/t/how-do-i-create-a-sphere/52661" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">draw a sphere</a>), and the mechanical engineers I&#x27;ve worked with have generally laughed at me for using it. That said, I can reasonably efficiently build things.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most people I know do not have the motivation or time to learn CAD software just to design and produce one or two parts every few months.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">CAD takes a while, too! Designing good parts requires thinking about the direction of layer lines, need for supports (or lack thereof), balance between strength and filament usage/time, and more, on top of just constructing a part that fits the desired functionality and looks pleasant.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Can we make CAD more accessible somehow? The advent of tools like TinkerCAD have certainly helped, but building useful parts within its constraints can be difficult. Maybe generative AI can democratize building shitty 3D models in the same way it democratized building shitty software? The attempts I&#x27;ve seen at extending generative AI to CAD haven&#x27;t been great.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The whole promise of 3D printing is customized parts built at much faster timescales and with less equipment than otherwise possible. But the typical home user only has the occasional need for a customized part. I use my printer about once a month, sometimes less, mostly because of the time investment of CAD.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="fitting-into-the-home">Fitting into the home</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2048" height="1248" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffdm%2Fkallax.jpeg&amp;w=2048&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffdm%2Fkallax.jpeg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffdm%2Fkallax.jpeg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">My 3D printer sitting atop an IKEA KALLAX. It is quite big!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Will 3D printers ever be common home appliances?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One could argue that the limited utility of them precludes that being a possibility. But the success of <a href="https://cricut.com/en-us/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Cricut machines</a> proves that &quot;maker&quot;-oriented devices for the home can be successful! The use cases for the Cricut are similar to those of an FDM 3D printer, they are about a similar size, and the process of using one is similar. But Cricut machines require far less tuning and setup, making useful things on a Cricut requires much less specialized knowledge, and I would guess that Cricut designs have a higher success rate on average than 3D prints.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I think the type of community surrounding hobbyist 3D printing also plays a role. 3D printing fans are often willing to tinker with their printers, want the ability to customize them, and want to bring their own slicer and tooling. That&#x27;s great for consumer advocacy, as evidenced by the failure of RFID-chipped vendor-locked filament among all but the most obscure printers. But it tends to direct the innovation in the 3D printing space towards chasing ultimate customization and featureset instead of building a cohesive user experience like Cricut has. I recognize that buying cheap hobbyist-oriented printers makes me part of this problem, but ultimately, that&#x27;s the type of printer that fits most home users&#x27; budgets.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I really love owning a 3D printer! And I really wish I got to use it more. I am a huge fan of customizing my living space to fit my life with objects that exactly meet my needs, fit my aesthetic, and work with the other objects in my home. 3D printing allows me to do this, and I think that&#x27;s wonderful.</p></div>]]></description>
            <link>https://breq.dev/2025/10/03/fdm</link>
            <guid isPermaLink="false">/2025/10/03/fdm</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[3D]]></category>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[I bought a Sony Mavica!]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1548" height="481" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fad.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fad.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fad.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I bought a Sony Mavica! And everything about this camera has been such a joy, I can&#x27;t help but write about it :)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <em>Digital Mavica</em> line of cameras saved images directly onto a floppy disk. The model I own is the <a href="https://camera-wiki.org/wiki/Sony_Mavica_FD90" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MVC-FD90</a> which was released in 2000. It captures images at a maximum resolution of 1280×960, but can also shoot at 640×480 to save space on the floppy disk (which can store around 20 photos at that resolution).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="inspiration">Inspiration</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Earlier this year at <a href="https://www.anthronewengland.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Anthro New England</a>, my girlfriend <a href="https://miakizz.quest/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a> was taking photos on her Mavica and let me try it a bit! It seemed like such a fun and whimsical camera to keep around.</p>
<div class="grid grid-cols-1 sm:grid-cols-[112fr,63fr] gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="768" height="1024" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ff0000948.jpg&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ff0000948.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ff0000948.jpg&amp;w=1920&amp;q=75"/></div><div class="grid grid-cols-1 gap-2 h-full justify-between"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1024" height="768" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ff0000379.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ff0000379.jpg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ff0000379.jpg&amp;w=2048&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1024" height="768" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ff0000525.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ff0000525.jpg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ff0000525.jpg&amp;w=2048&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1216" height="912" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ff0001317.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ff0001317.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ff0001317.jpg&amp;w=3840&amp;q=75"/></div></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I was intrigued by the camera, I was a bit intimidated by the floppy disks -- Mia has much more experience with retrocomputing than I do, and I worried that getting disks, a drive, pulling images off the camera, and keeping everything in good shape might be too difficult for me.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="swapfest-my-beloved">Swapfest my beloved</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It was at the most recent <a href="https://w1mx.mit.edu/flea-at-mit/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MIT Swapfest</a> that I picked up my beloved Mavica. I purchased it from some very persuasive transfems who had a table dedicated exclusively to Mavica stuff. I bought the model they recommended and they helped me get set up with a battery, strap, etc. I figured that, now that I&#x27;m getting settled into post-college life, now was a good time for me to pick up something new!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m a big fan of Swapfest: it&#x27;s almost more of a community gathering to me than a place to buy stuff. Whenever I go I see so many friends, former classmates, and colleagues I know from work. You get to know the tables that come back every month and the people who sell regularly. All in all, it&#x27;s a great way to spend a Sunday morning!</p>
<div class="grid grid-cols-1 sm:grid-cols-[63fr,112fr] gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="768" height="1024" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-004F.JPG&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-004F.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-004F.JPG&amp;w=1920&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1024" height="768" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-007F.JPG&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-007F.JPG&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-007F.JPG&amp;w=2048&amp;q=75"/></div></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="meticulous-unscheduled-disassembly">Meticulous unscheduled disassembly</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Mavica experts will know that aftermarket batteries tend to be a bit oversized compared to the originals. The folks who sold me mine made sure I was aware of this and even gave me a ribbon to put around the battery. However, in my excitement to try it out, I totally forgot about this and immediately put the battery directly in. I had to disassemble the camera to get it out!</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 max-w-3xl mx-auto"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffolded_open.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffolded_open.jpg&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Funder_drive.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmavica%2Funder_drive.jpg&amp;w=3840&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Big thanks to Sony for putting that plastic cover over the flash capacitor... that could&#x27;ve been a nasty shock otherwise 😬</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="my-first-disk">My first disk</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few tables at Swapfest hand out free floppy disks to passerby. I had accumulated a total of three disks and found that, miraculously, one of them actually worked! I had a mild panic moment when I realized the camera couldn&#x27;t always successfully read all of the photos on the disk, it turned out to be easily readable by a USB drive.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At the time, I didn&#x27;t have any way to read files off of the disk, so I just took as many as I could until it filled up.</p>
<div class="grid grid-cols-1 sm:grid-cols-[112fr,63fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-009S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-009S.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-009S.JPG&amp;w=1920&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="480" height="640" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-010S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-010S.JPG&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-010S.JPG&amp;w=1080&amp;q=75"/></div></div>
<div class="grid grid-cols-1 sm:grid-cols-[63fr,112fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="480" height="640" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-012S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-012S.JPG&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-012S.JPG&amp;w=1080&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-011S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-011S.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-011S.JPG&amp;w=1920&amp;q=75"/></div></div>
<div class="grid grid-cols-1 sm:grid-cols-[112fr,63fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-016S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-016S.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-016S.JPG&amp;w=1920&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="480" height="640" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-015S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-015S.JPG&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-015S.JPG&amp;w=1080&amp;q=75"/></div></div>
<div class="grid grid-cols-1 sm:grid-cols-[63fr,112fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="480" height="640" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-023S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-023S.JPG&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-023S.JPG&amp;w=1080&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-018S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-018S.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-018S.JPG&amp;w=1920&amp;q=75"/></div></div>
<div class="grid grid-cols-1 sm:grid-cols-[112fr,63fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="960" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-022F.JPG&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-022F.JPG&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-022F.JPG&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="480" height="640" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-024S.JPG&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-024S.JPG&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Ffirst-disk%2FMVC-024S.JPG&amp;w=1080&amp;q=75"/></div></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="getting-serious">Getting serious</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By this time, I had had enough fun with the camera that I figured I should actually get ahold of some disks and a floppy reader!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Mia advised against me buying a random floppy drive on Amazon, so at the recommendation of my friend <a href="https://tris.fyi/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tris</a>, I bought a Dell Latitude drive online for about $6. It&#x27;s designed to be put into a slot on the laptop, but it actually just has a normal Mini-USB connector. I bought some disks from an Amazon listing that looked legit (it was Maxell brand and had good reviews).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The disks arrived just in time for me to take them on a trip to New York City!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="848" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-001F.JPG&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-001F.JPG&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-001F.JPG&amp;w=3840&amp;q=75"/></div>
<div class="grid grid-cols-1 sm:grid-cols-[9fr,4fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="848" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F.JPG&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F.JPG&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F.JPG&amp;w=3840&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="848" height="1280" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-2.JPG&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-2.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-2.JPG&amp;w=1920&amp;q=75"/></div></div>
<div class="grid grid-cols-1 sm:grid-cols-[4fr,9fr] gap-2 max-w-3xl mx-auto my-2"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="848" height="1280" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-003F.JPG&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-003F.JPG&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-003F.JPG&amp;w=1920&amp;q=75"/></div><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="848" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-3.JPG&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-3.JPG&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-002F-3.JPG&amp;w=3840&amp;q=75"/></div></div>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="848" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-004F.JPG&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-004F.JPG&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fnyc%2FMVC-004F.JPG&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="data-recovery-adventures">Data recovery adventures</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After getting back from NYC and finally being able to read the disks, I discovered that a few of the photos weren&#x27;t readable. This wasn&#x27;t a big deal (the photos I cared most about were fine), but it definitely taught me to be less careless with floppies. My backpack has dozens of tiny magnets in it and I struggled to find a spot far enough away from all of them!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1280" height="848" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmavica%2Fdamaged_image.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmavica%2Fdamaged_image.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmavica%2Fdamaged_image.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Mia recommended I use <a href="https://www.gnu.org/software/ddrescue/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ddrescue</a> to try to recover parts of the images. It ended up not giving me anything more than I got from copying files over, but was interesting to learn. I could definitely see it coming in handy in the future.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">ddrescue</span><span class=""> /dev/sda hdimage </span><span class="" style="color:#404040">mapfile</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">mkdir</span><span class=""> /tmp/hdimage</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">mount</span><span class=""> -o loop hdimage /tmp/hdimage</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">cp</span><span class=""> /tmp/hdimage/* </span><span class="" style="color:#404040">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">umount</span><span class=""> /tmp/hdimage</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To reformat the disks, I used a tool called <a href="https://manpages.ubuntu.com/manpages/jammy/man8/ufiformat.8.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ufiformat</a> to re-lay the tracks and then used the Mavica&#x27;s built-in disk format tool to recreate the filesystem.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> ufiformat /dev/sda</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="more-to-come">More to come</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thus far I&#x27;m really enjoying shooting on the Mavica! Limiting myself to a specific number of shots per disk helps me be more thoughtful than with a modern digital camera, but unlike with film, if I take a bad shot I can just delete the file -- I haven&#x27;t wasted anything.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dealing with floppy disks as a medium is much more fun and less stressful than I anticipated! The disks themselves are cheap, devices for reading and writing them are cheap, software support is quite good even on modern machines, and with a little care they&#x27;re easy enough to keep in working order.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve been in a bit of a lull with photography lately, but I think I might&#x27;ve found something to get me back into it!</p></div>]]></description>
            <link>https://breq.dev/2025/07/01/mavica</link>
            <guid isPermaLink="false">/2025/07/01/mavica</guid>
            <category><![CDATA[retro]]></category>
            <category><![CDATA[photos]]></category>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[My Experience with Gender]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve noticed that I can be far too quick to assume that the experience of others matches my own. This is one of the reasons I&#x27;ve held off on writing about being transgender fo so long: I figured I would just be telling my transgender audience things they already know. But over the past four years, I&#x27;ve realized that the way I experience gender has substantial differences to the way every other trans woman I&#x27;ve talked to experiences it, just as every trans woman I know has a unique and deeply personal experience compared to any other. I am to highlight this broad range of experiences when I portray my own.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The other reason I have held off on discussing my experience is that I often mistake the hesitance of my cisgender friends to ask questions as apathy. While I know many other transgender people are less open about their experience (which is understandable!), I have always been overjoyed to have conversations about gender and my existence as a trans woman with the cis people in my life. I hope this post can lay out the groundwork and serve as an invitation for people I know to start these conversations with me.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My wonderful girlfriend Mia has also written a blog series about being transgender, which you can find <a href="https://miakizz.quest/posts/trans1" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>. You should read that one after this -- I always look up to her wisdom when it comes to big topics like this :)</p>
</blockquote>
<br/>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Update: My girlfriend <a href="https://avasilver.dev/blog/transition" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a> and my friend <a href="https://tris.fyi/blog/thoughts-about-gender.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tris</a> have also written very insightful posts about this!</p>
</blockquote>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="early-transition">Early Transition</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A commonality between most transgender people I know is that our decision to transition is defined by strong emotions. Deciding to transition is deciding between something very known -- your everyday life, as it is now -- and something entirely unknown. It is impossible to know beforehand what emotions you will experience after coming out. Nobody can tell you with perfect accuracy if transitioning is something that would make you happier.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The only way to know for certain is to try it in a truly immersive sense: name, pronouns, and the way you present yourself in every aspect of life. Unfortunately, we live in a society where this is often somewhere on the scale of impractical to impossible. And thus, most trans people are going in blind.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In my case, the strong emotion that determined my decision to transition was fear. Not fear of stepping into this unknown experience, but fear of <em>not</em> doing so. I was depressed in the years leading up to the start of my transition (for me, the last two years of high school). Had I not transitioned, I may not have made it to here today. Despite being a complete unknown, transition felt like my best chance of survival.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I know my story is far from unique, it brings me happiness that there are those for which this decision is led primarily by positive emotions, too.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I chose to delay my social transition until starting college since moving cities let me avoid or postpone coming out to people I already knew. While diving headfirst into being &quot;Brooke, she/her&quot; was overwhelming at times, it was better than having to hold a &quot;coming out&quot; discussion with everyone in my life at the time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is a cruel irony that one of the most difficult parts of transition -- having to explain to everyone in your life what being transgender is and answer countless questions about something so deeply personal to you -- happens so early on in the process, before you have had a chance to build confidence in yourself or even grow to understand yourself. It makes me happy to see communities in which gender fluidity and questioning are more widely accepted and supported.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the first year of my transition, I honestly didn&#x27;t have the energy to think critically about my own experience of gender. I knew that presenting femininity felt nice, but the details didn&#x27;t matter -- I was too focused on learning how to buy clothes that I like (and throwing out everything I knew about how clothes were &quot;supposed to&quot; fit), learning how to take care of long hair, and overcoming my fears, one step at a time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I take my confidence for granted nowadays, but I still vividly remember my first time leaving my dorm room wearing a skirt. I remember noticing how much my hands were shaking when reaching for the elevator button, and trying to make myself small in any way possible. I am six feet tall, and while I enjoy being loud and confident nowadays, there was a long stretch of time when I just wanted to blend into the background.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="gaining-confidence">Gaining confidence</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The next year of my transition is when I finally started to feel like I could &quot;take up space&quot; in social gatherings. I dyed my hair bright pink for the first time and started to assert myself more. I finally started to feel okay with using the women&#x27;s restroom instead of walking halfway across campus for a gender-neutral one.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I made a few false starts at voice training, over time I realized that I had become comfortable with my voice as it is without deliberate training. While I&#x27;m less likely to be gendered correctly by strangers in public, I can still feel safe in that my friends, peers, and coworkers will not see it as evidence that I am any less of a woman. While I&#x27;m not ruling out voice training in the future, it would be on my terms.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My goal in transition is not to assimilate into cisnormative society. It is to wake up each morning and present myself to the world in a way that sparks joy.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Being transgender does not define my life nowadays to the extent it used to a few years ago. On one hand, the day-to-day stresses of my early transition years have subsided, and I am much happier as a result. On the other hand, I do think my <em>trans-ness</em> -- my experience of transitioning, my presence in transgender social circles, and the unique perspective I have as a result -- is one of the most notable pieces of my life.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I would say that in my day to day life, I am a very happy person. I would also say that my trans-ness is an aspect of my life that brings me happiness. This would have been unfathomable to my past self.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="finding-stability">Finding stability</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The word &quot;transition&quot; implies that the process of changing one&#x27;s gender happens over a well-defined time interval. This is, in my eyes, inaccurate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In one sense, my transition &quot;ended&quot; when I made the commitment to myself that I was a woman. The social steps -- changing the way I dressed, coming out to friends and family, updating my legal name -- were all just logistics.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In another sense, transition is never &quot;done.&quot; As my body cannot naturally produce estrogen, I will keep taking hormone replacement therapy for as long as I live. The style of clothing I wear will continue to shift throughout my life. There are still people who knew me before transition and who I will need to come out to if we ever cross paths again.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is only recently that I have felt stable enough to reconsider some of the fundamental questions of my transition. I enjoy the way I dress, the way people refer to me, and the effects that feminizing HRT brings to my body. But the label of &quot;transgender woman&quot; was less something I decided on and more something that was handed to me as a result of this.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve begun to question whether &quot;non-binary&quot; describes the way I experience gender. I&#x27;ve always felt a bit of dissonance with being referred to as a woman, and for the longest time, I wrote that off as imposter syndrome. But four years in, I&#x27;m as confident as can be, and that feeling is still there.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Perhaps this questioning could only happen this many years in. It&#x27;s taken time for &quot;they/them&quot; pronouns to stop feeling like a way for people to avoid acknowledging my transition and gender, and start feeling like them acknowledging the complexities of my gender beyond the binary.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Alternatively, perhaps this was down to a lack of representation of non-binary people in my social groups. While early in transition, I sought out friends with similar experiences to me, and ended up in groups primarily consisting of binary trans women. At present, several close friends of mine are non-binary, and I&#x27;ve been able to have many more interesting conversations about gender with people outside the gender binary. These conversations have undoubtedly been a catalyst for questioning my own gender identity.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There has been an ongoing bit on this website since <a href="https://github.com/breqdev/breq.dev/commit/d054f38bb5fc75b07d22fcc479dee654eb7de815" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">three years ago</a> in which, at some random probability, the pronouns displayed on the homepage will be either &quot;she/her&quot; or &quot;she/they&quot;. I wish I could remember what was in my head when I wrote that.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One explanation is that labels have always felt unimportant in relation to my own experience. While I recognize how powerful labels can be in helping people explain their identity and connect to others, I find their imperfect fit frustrating. I care less about whether I am called &quot;she&quot; or &quot;they,&quot; and far more about the motivations of those calling me this. Am I being called &quot;they&quot; in an attempt by the speaker to avoid recognizing or legitimizing my identity as a woman? Or am I being called &quot;they&quot; in recognition of the fact that my identity does not perfectly map onto the gender binary?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I hope that in five years I can re-read this post and find myself disagreeing with parts of it. I hope that my perception of myself, of transness, and of the way I fit into society will shift over time. I want to continue thinking about, exploring, and experimenting with my own sense of gender identity.</p></div>]]></description>
            <link>https://breq.dev/2025/06/22/gender</link>
            <guid isPermaLink="false">/2025/06/22/gender</guid>
            <category><![CDATA[gender]]></category>
            <pubDate>Sun, 22 Jun 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[My Hair Routine]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I have very thick, curly, bleached hair, which is about as &quot;hard mode&quot; as you can get. I have gotten <em>so much</em> bad advice from people about how to care for it. After a few years and some tips from friends, here&#x27;s the system I&#x27;ve ended up with!</p>
<p class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-600">I&#x27;ve added products I use in the toggle elements below, since I think providing specific examples can be useful, but I don&#x27;t want this to come across as too much of an endorsement. I know enough about this topic to solve problems, but I&#x27;m not 100% sure I&#x27;ve landed on the best set of products for my hair. You, dear reader, likely have different hair with different needs anyway.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2311" height="2311" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fhair%2Futah.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fhair%2Futah.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Last year at the University Rover Challenge. I&#x27;ve grown out my hair quite a bit since then!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="requirements">Requirements</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To give some context, here are the requirements I have for my hair routine:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">It must actually solve the tangle problem (this one is very hard)</li>
<li class="my-2 pl-2">Regular maintenance must be a reasonably straightforward process, since I am lazy</li>
<li class="my-2 pl-2">Products should be as easy to get as possible (ideally, I should be able to get everything from a CVS or Target)</li>
<li class="my-2 pl-2">The process should not impede my ability to travel -- I should easily be able to pack a small, TSA-legal bag for week-long trips with necessities</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="regular-maintenance">Regular maintenance</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Whenever I shower, there are two main hair-related tasks I focus on. Detangling is the endless struggle I face, and what the majority of my routine is built around. Washing ends up essentially being a by-product of the detangling process, not the other way around.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I generally almost never use shampoo! My regular process consists of two steps: a washing conditioner (aka &quot;cowash&quot;) and a leave-in conditioner. I first add the washing conditioner at the roots of my hair, scrub it in, let it sit while I wash my body, loosely detangle my hair with my fingers, then rinse it out. Then, I add leave-in conditioner, focusing on the roots of my hair, and use a wide-tooth comb to continue detangling.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Often when I mention that I don&#x27;t use shampoo, the response I hear is &quot;my hair is so oily, I could never,&quot; and this is often valid! I am lucky that oily hair is a problem that I do not face, and I find cowash to help with tangles much more, so it is the solution I use.</p>
<details class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-600"><summary>Products</summary><p class="mx-auto my-4 max-w-prose font-body text-lg ">I use <em>As I Am Coconut Cowash</em> as my washing conditioner. For leave-in conditioner, I use either <em>As I Am Classic Leave-In Conditioner</em> or <em>Kinky-Curly Knot Today</em>. I use a <em>Wet Brush Detangling Comb</em> with the zig-zag teeth.</p></details>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="less-frequent-maintenance">Less frequent maintenance</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Very occasionally, I will use shampoo if I need to clean my hair more vigorously than washing conditioner allows. I&#x27;ve <em>heard</em> that sulfate-free types are better for &quot;low-shampoo&quot; routines like mine, since they don&#x27;t leave oils as much.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I do also have a styling cream I can use, but I don&#x27;t find myself reaching for it very much.</p>
<details class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-600"><summary>Products</summary><p class="mx-auto my-4 max-w-prose font-body text-lg ">The shampoo I use is <em>Herbal Essences Bio Renew Aloe + Eucalyptus Scalp Balance Shampoo</em>. The styling cream is <em>Shea Moisture Curl Enhancing Smoothie</em>.</p></details>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="coloring">Coloring</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2510" height="2850" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fhair%2Fbrown_hair.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fhair%2Fbrown_hair.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Me in 2021 at the start of college -- I looked pretty different at the time!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, bright pink is not the natural color of my hair, so I need to dye it every month or so to keep it colorful!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I dye my hair DIY-style, for two main reasons. The most obvious is that I started off with hair dye as a student -- I can afford to get it professionally dyed now, but I couldn&#x27;t back then. But more importantly, I view it as a really fun activity to do with people I&#x27;m close with. The joy of doing it with someone I know vastly outweighs the somewhat inconsistent results I get :)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I don&#x27;t always use the same shade of dye each time, but the resulting color stays pretty consistent since it blends with the residual color from prior applications.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Every few dye sessions, I touch-up bleach the roots of my hair. The first initial bleach was a massive ordeal that took three boxes, but since then, I&#x27;ve just used one box every few months.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also dye my girlfriend <a href="https://avasilver.dev" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a>&#x27;s hair with the same setup.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="6000" height="4000" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fhair%2Fold_color.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fhair%2Fold_color.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">An older shade of dye I previously used. Picture from 2023.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I use a semi-permanent dye (doesn&#x27;t require developer) since it&#x27;s much less stressful and easier to apply, and it seems to last as long as the &quot;permanent&quot; ones I&#x27;ve used. (I&#x27;ve accidentally fallen asleep with hair dye in and been fine!) I used to use a boxed &quot;permanent&quot; dye (and even once went on a 2+ hour bus journey into the a CVS in the Boston suburbs when the chain stopped stopped carrying it just so I could buy up the remaining stock), but in hindsight I didn&#x27;t like the color it gave as much.</p>
<details class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-600"><summary>Products</summary><p class="mx-auto my-4 max-w-prose font-body text-lg ">I use Arctic Fox dyes, mostly <em>Virgin Pink</em> but sometimes <em>Electric Paradise</em>. I&#x27;ve tried <em>Girls Night</em> and <em>Porange</em> but haven&#x27;t continued to use them. Ava&#x27;s hair is <em>Purple AF</em>. I also use a mixing bowl and brush from Arctic Fox.</p><p class="mx-auto my-4 max-w-prose font-body text-lg ">For bleach, I usually buy a generic boxed kind such as <em>Colorista All Over</em>. I don&#x27;t use the included toner since I&#x27;ll immediately put color on top of it anyway.</p><p class="mx-auto my-4 max-w-prose font-body text-lg ">The original box dye I used was <em>got2b Metallics Sakura Pink</em>.</p></details>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="color-conditioner">Color conditioner</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few people I know have recommended creating a color conditioner by combining my conditioner with a few drops of hair dye. I&#x27;m interested in this idea to supplement my regular re-dye process, but there are a few reasons I&#x27;ve held off:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">I worry about color bleed from my hair staining things -- currently this is only a problem for the first few washes after a dye session, but if I am constantly reapplying dye, it could cause more problems</li>
<li class="my-2 pl-2">I like being able to loan my hair products to friends who are staying the night, etc., most of whom do not want pink hair</li>
<li class="my-2 pl-2">I&#x27;m happy with the way my two types of conditioner work, and I don&#x27;t want to mess with the system :)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, I will probably still try this at some point once the time is right!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="travel">Travel</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2159" height="3473" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fhair%2Fhotel.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fhair%2Fhotel.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Me this summer traveling for the University Rover Challenge.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I take a lot of overnight or weekend trips, and I like not having to worry about having my favorite hair stuff with me, so I keep my hair products in 3oz travel containers. It&#x27;s also quite helpful for fitting all of the containers onto limited shower shelf space, and for dispensing small amounts of cowash (since mine comes in a tub).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I haven&#x27;t found a good solution for labeling these yet, and I&#x27;m worried that I&#x27;ll eventually mistake two similar-looking products, but so far I&#x27;ve been getting by with occasionally re-writing labels on them with sharpie.</p>
<details class="mx-auto my-4 max-w-prose rounded-2xl bg-gray-200 px-4 py-2 font-body text-lg dark:bg-gray-600"><summary>Products</summary><p class="mx-auto my-4 max-w-prose font-body text-lg ">I just bought a random set of containers from Target, I can&#x27;t find the exact one I got anywhere. But the important part is just that it&#x27;s 3-4 small bottles with squirt lids. Search &quot;TSA Travel Container Set&quot; or &quot;TSA Travel Bag Kit&quot; on the website and you&#x27;ll find similar stuff.</p></details>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This system has worked for me for the past ~4 years! I think it balances my needs well and I&#x27;m proud that I&#x27;ve found something that works.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2000" height="1333" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fhair%2Flab.jpg&amp;w=2048&amp;q=75 1x, /_next/image?url=%2Fimages%2Fhair%2Flab.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fhair%2Flab.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A recent-ish picture of me having fun in the Rover lab, with a slightly oranger shade of dye.</p></div>]]></description>
            <link>https://breq.dev/2025/06/21/hair</link>
            <guid isPermaLink="false">/2025/06/21/hair</guid>
            <category><![CDATA[edc]]></category>
            <pubDate>Sat, 21 Jun 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Tiny Devices I Love]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve developed a strong appreciation for small devices that solve problems for me. While larger versions of these tools exist, there are a few reasons why these tiny tools hold a special place in my heart.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first is that I have spent the last several years living in small dorm rooms and now live in a relatively small apartment. While I wish I had the space for a dedicated electronics workbench, the practical reality is that my tools need to fit either on my normal-sized desk or be stored away in a drawer. The second is that, both for my work with the Northeastern Mars Rover Team and for my own projects, I do occasionally end up needing to fix, debug, or bodge something together in locations which are less conducive to this type of work (such as the middle of a grassy field).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The criteria I decided on for devices in this category include:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">The device must be &quot;cute.&quot; More formally, this includes things that are about the size you could fit into a large pocket. It also means that the device must be smaller than most others in its class.</li>
<li class="my-2 pl-2">The device must be something I would carry somewhere to perform a task. While I love my tiny <a href="https://shop.netgate.com/products/1100-pfsense" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Netgate 1100</a> (a gift from <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a>), it&#x27;s not something I would transport to somewhere for a temporary installation.</li>
<li class="my-2 pl-2">The device must be good at what it does. It must not sacrifice functionality to become its small size.</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Acknowledgements:</strong> This post was loosely inspired by my friend Hunter&#x27;s <a href="https://pixilic.com/devices-of-all-time" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Devices of All Time</a> post. Many of these devices were gifts from close friends!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="ifixit-moray-driver-kit">iFixit Moray Driver Kit</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="2870" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fifixit.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fifixit.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Gift from <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a> and <a href="https://pixilic.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Hunter</a>!</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a screwdriver kit including a small screwdriver and a large collection of bits. It comes in a hard plastic case, the lid of which doubles as a tray for storing screws.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve used the Moray&#x27;s older sibling, the Mako, extensively on the rover team, but haven&#x27;t had my own iFixit kit until recently! I like to flip the bits I&#x27;m using for a project down to visually distinguish them from the rest and make them easier to grab.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Very small and cute. Easy to throw into a backpack or tool bag.</li>
<li class="my-2 pl-2">Extremely versatile, good selection of bits. Replaces the need to carry a whole range of screwdriver sizes.</li>
<li class="my-2 pl-2">Handle size is large enough for &quot;big screwdriver&quot; tasks, but small enough to allow for delicate work.</li>
<li class="my-2 pl-2">Spots for bits are labeled with both an icon and text.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">No 2.0mm hex bit. (I encounter M2.5 screws enough that I&#x27;m surprised this was omitted!)</li>
<li class="my-2 pl-2">While additional bits are available, there aren&#x27;t any additional spots in the case to hold them. (It&#x27;s possible to store them within the screwdriver, but that makes them harder to access.)</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="fnirsi-dps-150-dc-power-supply">FNIRSI DPS-150 DC Power Supply</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fpower-supply.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fpower-supply.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Gift from <a href="https://miakizz.quest" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a>!</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a benchtop DC power supply, which is an essential tool for electronics prototyping. This type of power supply is useful as it precise voltage control and current monitoring and limiting to protect your circuit. It provides power through &quot;banana plugs&quot; or directly to wires via the two terminals.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Taking power from a USB-C port greatly reduces the size of the unit.</li>
<li class="my-2 pl-2">Screen is quite readable and useful for precise adjustments.</li>
<li class="my-2 pl-2">Maximums of 30 volts and 5 amps.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Display hinge struggles to hold itself up.</li>
<li class="my-2 pl-2">Voltage/current input is a bit unintuitive.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="glinet-opal-ac1200-router">GL.iNet Opal (AC1200) Router</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fgl-inet.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fgl-inet.jpg&amp;w=3840&amp;q=75"/></div>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fgl-inet-ports.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fgl-inet-ports.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a &quot;travel router&quot;: A small, lightweight router and wireless access point powered over USB-C. This one in particular is quite cheap and actually ran my home network for a bit until I was able to set up the pfSense unit I use now. It&#x27;s also saved the day at Rover events a few times when I&#x27;ve used it to establish a wireless network in areas otherwise slightly too far from a nearby building&#x27;s coverage.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">USB type-C power input.</li>
<li class="my-2 pl-2">Two LAN Ethernet ports.</li>
<li class="my-2 pl-2">Strong featureset provided by OpenWRT.</li>
<li class="my-2 pl-2">Antennas fold in for compactness when not in use.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Runs a very old patched version of OpenWRT, not the mainline version.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="radioshack-22-182-digital-multimeter">RadioShack 22-182 Digital Multimeter</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3072" height="4096" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fmultimeter.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fmultimeter.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>This was a gift from... my dad probably? I&#x27;ve had it as long as I can remember.</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a multimeter: a device that measures the voltage difference between two points, the current flowing through itself, or the resistance of an electrical component. While some meters support &quot;autoranging,&quot; this one requires that you specify the range of values you expect the quantity you measure to be in (e.g. 200V, 20V, or 2V). I honestly prefer manual ranging meters, since dialing in the voltage at the start is usually easy to do and the meter responds more quickly when it doesn&#x27;t need to go through the autoranging sequence.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Carrying case keeps probes inside.</li>
<li class="my-2 pl-2">No need to switch probes between sockets for current vs voltage measurement.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Current measurements limited to 200mA.</li>
<li class="my-2 pl-2">Case is slightly too small for probes to fit comfortably.</li>
<li class="my-2 pl-2">No beeper for continuity testing.</li>
<li class="my-2 pl-2">Uses an uncommon 12V battery.</li>
<li class="my-2 pl-2">Not as precise as larger meters.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pinecil-v2">Pinecil V2</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Fpinecil.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Fpinecil.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Gift from <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a>!</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a soldering iron: a tool that heats its tip up to a high enough temperature to melt solder (metal) and join electrical wires. Other potential uses include pushing heat-set metal inserts into plastic parts. This is a temperature-controlled iron, which is essential to allow heating solder efficiently without damaging components from excessive heat.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">USB-C power input.</li>
<li class="my-2 pl-2">Readable monochrome OLED display.</li>
<li class="my-2 pl-2">Indicators for iron cooldown.</li>
<li class="my-2 pl-2">IMU-based standby power-off.</li>
<li class="my-2 pl-2">Easily swappable tips.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Doesn&#x27;t include a stand or case of any sort.</li>
<li class="my-2 pl-2">Some intermittent issues with connectivity between the iron and tip.</li>
<li class="my-2 pl-2">Two-button based interface is sometimes unintuitive to use.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="aioc-all-in-one-cable">AIOC (All-in-One Cable)</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpocket-size%2Faioc.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpocket-size%2Faioc.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>This is from a run made by <a href="https://miakizz.quest/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a>, although I believe I paid her back for it? Mia if you&#x27;re reading this and I owe you money, please let me know :)</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a device with two purposes: to upload configuration data (&quot;codeplugs&quot;) to handheld radios, and to connect a handheld radio as a soundcard to a computer. The former use case is nice for programming radio channels as it allows configurations to be easily created and distributed. The latter use case enables computer modulation techniques such as AX.25.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Likes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Smaller than conventional radio programming cables.</li>
<li class="my-2 pl-2">Alignment between 2.5mm and 3.5mm jacks is not sturdy.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dislikes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Can&#x27;t be used to program my Retevis RT3S.</li>
<li class="my-2 pl-2">3D printed case is flimsy.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You&#x27;ll notice I didn&#x27;t put direct links to where to buy any of these (and some of them, like that Radio Shack multimeter, are likely impossible to get these days). My intent is to show how these tools fit the style of work that I do and enable me in ways traditional alternatives don&#x27;t, and to maybe help you consider how the tools you use in your work shape the projects you take on and the methods you use.</p></div>]]></description>
            <link>https://breq.dev/2025/05/02/pocket-size</link>
            <guid isPermaLink="false">/2025/05/02/pocket-size</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[edc]]></category>
            <pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Mini-Rack Homelab]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Frack.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fminirack%2Frack.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I recently reorganized all of my home networking hardware into a 10&quot; rack that sits in an IKEA KALLAX shelf next to my desk.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="why-mini-rack">Why Mini-Rack?</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was motivated to take this project on by Jeff Geerling&#x27;s <a href="https://mini-rack.jeffgeerling.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Project Mini Rack</a> website, which collects links to hardware and guides for rackmount builds in the 10&quot; form factor. I&#x27;ve wanted to build out a rackmount system for ages, partly because I want a rigid frame for my networking hardware (as opposed to a loose collection of boxes sitting atop a shelf), and partly because I love the aesthetics of rackmount gear. While I could undoubtedly achieve the same goals in this project without using a rack at much less cost, I&#x27;ve wanted to play with something in the rackmount form factor for <em>years</em>. I had largely written it off as impractical given the small size of the apartment, but the proliferation of 10&quot; gear has made it possible to set up a compact rackmount homelab system.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="goals-and-improvements">Goals and Improvements</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My existing home network setup is designed for a small apartment which I share with my girlfriend <a href="https://avasilver.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a>. We get internet service from a cable connection (no fiber here unfortunately), and we bought a basic Arris SURFboard modem. From here, we use a Netgate pfSense router gifted by my other girlfriend <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a>, which also runs a site-to-site Wireguard VPN connecting to her home network. The router connects to a Ubiquiti U6 Mesh access point which both provides our personal Wi-Fi network and functions as a working <a href="https://eduroam.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">eduroam</a> service provider with client isolation (the details of which will remain a mystery).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The wired side of the network runs through a Netgear GS105 switch which I got at <a href="https://w1mx.mit.edu/flea-at-mit/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MIT Swapfest</a> for five bucks. (My switch was actually, unbeknownst to me, featured on my friend <a href="https://pixilic.com/devices-of-all-time" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Hunter&#x27;s blog</a> last year.) From here, wired connections run to my desktop and Ava&#x27;s desktop.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My primary goal for this project was to reduce the clutter on my shelf and replace it with something that looked cool. I also wanted to finally have a good way to self-host things again. While I&#x27;m happy to keep most services hosted online, it would be nice to have some services running locally where convenient.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="networking-setup">Networking Setup</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Fempty-rack-on-shelf.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fminirack%2Fempty-rack-on-shelf.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I chose a <a href="https://deskpi.com/products/deskpi-rackmate-t1-rackmount-10-inch-4u-server-cabinet-for-network-servers-audio-and-video-equipment" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RackMate T0</a> case, since it would fit within a single shelf of the IKEA KALLAX next to my desk (and because the next size up was 8U and I doubted I would have enough hardware to justify that). The case arrived with a broken top acrylic panel, but the folks at GeeekPi (DeskPi) shipped me a replacement quickly without any hassle! In the meantime, I got to start building out the rest of the rack.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">No rack is complete without a patch panel, and mine needed some way to pass cables through from the front-mounted Ethernet ports on my router and network switch. I chose a <a href="https://deskpi.com/products/deskpi-rackmate-accessory-10-inch-network-switch" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">12-port model from DeskPi</a>, designed for the rack I used. This provides more ports than I need for my modem connection, access point, and for our desktops, but the Ethernet ports can be swapped for any keystone block in case I want to use them for any other purpose.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One problem I wanted to solve with this design was the bundle of power bricks tangled up behind my shelf. I saw the <a href="https://deskpi.com/products/deskpi-dc-pdu-lite-7-ch-0-5u-for-deskpi-rackmate-t1?_pos=1&amp;_sid=782266501&amp;_ss=r" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">DeskPi PDU Lite</a> available, and decided to use it in combination with a 12V 8A power supply to power devices in the rack. Right now, it powers my modem, router, and GS105 switch, but there are 4 additional channels which could be used for an SBC, hard drive mount, etc.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Frack-v1.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fminirack%2Frack-v1.jpg&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="proxmox">Proxmox</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After using the rack for some time, I finally broke down and spent the money on an actual server. I went with a refurbished Lenovo ThinkCentre M710q that I bought for around $75, since it had relatively modern hardware, would fit within a 10&quot; 1U shelf, and uses relatively low power.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3029" height="2818" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Fgrapefruit.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fminirack%2Fgrapefruit.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few friends recommended I try <a href="https://www.proxmox.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Proxmox</a> for this project, so I decided to give it a try! It supports full VMs as well as containers, which is nice. Previously I&#x27;ve relied on solutions like Dokku which only support container-level virtualization.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="unifi-network-controller">UniFi Network Controller</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Shortly after I moved, I <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2024/10/22/home-network">invested in a Ubiquiti access point</a> for the new apartment, and I absolutely do not regret my decision. However, this AP has one minor annoyance for home use compared to a traditional residential access point. Instead of being configured via a web UI running locally on the device, Ubiquiti APs are configured by a central server. This approach is great for managing a large deployment of access points but slightly annoying for a home setup.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1493" height="961" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Funifi.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fminirack%2Funifi.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fminirack%2Funifi.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, there&#x27;s an upside: Ubiquiti&#x27;s controller software can be <a href="https://help.ui.com/hc/en-us/articles/220066768-Updating-and-Installing-Self-Hosted-UniFi-Network-Servers-Linux" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">self-hosted rather easily</a>! I decided to do this first, and after spending 5 minutes trying to find where to install container templates from, I got the Unifi controller running on an Ubuntu container. After <a href="https://help.ui.com/hc/en-us/articles/360008976393-Backups-and-Migration-in-UniFi" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">exporting and importing</a> my config to transfer it to the new controller, I can finally manage access point settings without needing to boot my desktop!</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="homeassistant">HomeAssistant</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ava is a big fan of smart home devices, so we&#x27;ve ended up with a dozen smart light bulbs in the apartment. While running these through Google Home works great for basic usage, the lack of an extensible API is something we&#x27;ve missed dearly. Our home has already started to collect <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/matrix2">weird and wonderful homemade devices</a>, and an open platform would give us much greater opportunity for fun shenanigans.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1493" height="961" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Fhomeassistant.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fminirack%2Fhomeassistant.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fminirack%2Fhomeassistant.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the recommendation of a friend, we&#x27;re trying out <a href="https://www.home-assistant.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">HomeAssistant</a> to run our devices. Most of our devices are based on the <a href="https://www.home-assistant.io/integrations/matter/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Matter</a> standard, which should (in theory) enable fully local control. The process of importing Matter devices went pretty smoothly, although we needed to tediously rename each device we added. We do have a few weird bulbs we bought to fit in an IKEA fixture that used the Tuya app, but HomeAssistant had an integration for those as well which seems to work perfectly.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="reverse-proxy">Reverse Proxy</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With all of these services on the same box, I&#x27;ll definitely need to set up a reverse proxy for these services. My go-to for this is usually <a href="https://nginx.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nginx</a>, but I decided to try <a href="https://caddyserver.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Caddy</a> instead to avoid the hassle of setting up HTTPS certificates. While I did find it more annoying to debug, adding new hosts and proxying new services is turning out to be a breeze so far!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1493" height="961" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Fproxy.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fminirack%2Fproxy.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fminirack%2Fproxy.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The reverse proxy also serves a basic static site, mostly as an excuse for me to make a cute drawing of the rack and my desktop.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="syncthing">Syncthing</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For my &quot;hot workspace&quot; of files (documents, code, etc), I like to store things in <a href="https://syncthing.net/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Syncthing</a> so they&#x27;re available on each of my devices. It&#x27;s the best cross-platform tool for file sync that I&#x27;ve found, and is really easy to self-host!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1493" height="961" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Fsyncthing.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fminirack%2Fsyncthing.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fminirack%2Fsyncthing.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I created a container and followed the Syncthing setup steps for Ubuntu. The only snag I hit was that I couldn&#x27;t remotely login to set up the GUI from a different device. Turns out the approach is to edit the config to replace <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">127.0.0.1</code> with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0.0.0.0</code>, which is pretty straightforward:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">nano /root/.local/state/syncthing/config.xml</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The documentation for <a href="https://docs.syncthing.net/v1.0.0/users/autostart.html#linux" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">setting it up to run with systemd</a> also worked perfectly.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="nas">NAS</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tend to keep only actively used files on Syncthing since my devices are constantly running out of storage space. For long term storage, Ava and I used to use NextCloud, but found it annoying to keep it running and stable. Thus, we switched to a basic Samba and WebDAV share for this.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I don&#x27;t immediately have hard drives ready to add, I was able to get started by just setting up a container and using a folder on its root volume. Installing Samba was straightforward. I do eventually want to install WebDAV as well, but that will have to wait for another time (I&#x27;ve been sitting on this blog post for far too long!)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="octoprint">OctoPrint</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://octoprint.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OctoPrint</a> is a server for managing a 3D printer remotely. I recently dusted off my old Ender 3 Pro and started <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/matrix2">making stuff with it again</a>, and now that I&#x27;ve graduated and work a normal work schedule, I have fewer opportunities to check on a print throughout the day.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1493" height="961" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fminirack%2Foctoprint.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fminirack%2Foctoprint.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fminirack%2Foctoprint.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to use the popular <a href="https://github.com/paukstelis/octoprint_deploy" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">octoprint_deploy</a> for installation. Since the README warns against trying to deploy in an LXC container, I made this host a full Ubuntu Server VM. I was able to pass through the USB device easily by its vendor and product ID, and everything worked on the first try! I don&#x27;t have a USB camera for remotely monitoring yet, but I might decide to add one if I find this useful.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="general-purpose-ubuntu-and-windows-servers">General purpose Ubuntu and Windows servers</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I find myself occasionally booting into Windows 11 for things, most recently for uploading a codeplug to my OpenGD77 radio. Since I almost never use this partition otherwise (the video games I play all support Linux at this point), I really only need a basic Windows 11 box with USB passthrough support. I got the idea for this from my girlfriend <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a> who uses a Windows VM for similar purposes. I also set up an Ubuntu desktop machine at the same time, mostly because it was easy to do so.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results-and-conclusions">Results and Conclusions</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">First off, I would like to think Ava for putting up with me occasionally disrupting our home internet to perform upgrades or maintenance. Thanks also to <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a>, <a href="https://pixilic.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Hunter</a> and <a href="https://tris.fyi/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tris</a> for helping me troubleshoot on the countless occasions I accidentally shot myself in the foot with Proxmox. This project veered farther into the realm of networking and system administration than I ever had before, and I couldn&#x27;t have done it without help.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Taken as a whole, this is probably one of my more practical projects as of late: I&#x27;m finally able to clean up my convoluted file sync situation, I&#x27;ve gained the ability to remotely manage stuff on my home network, and I&#x27;ve already enabled a few helpful automations with HomeAssistant.</p></div>]]></description>
            <link>https://breq.dev/projects/minirack</link>
            <guid isPermaLink="false">/projects/minirack</guid>
            <category><![CDATA[networking]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Tue, 29 Apr 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[orb.breq.dev]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4096" height="3072" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Forb.breq.dev%2Ffado.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Forb.breq.dev%2Ffado.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Sitting right next to my desk as I type this is an <a href="https://www.ikea.com/us/en/p/fado-table-lamp-white-70096377/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">IKEA FADO</a> lamp fitted with an RGBW LED bulb. Among friends, it&#x27;s become affectionately known as &quot;the orb&quot; due to its unique shape.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="implementation">Implementation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project uses quite a few moving parts to pipe lighting data through from the user&#x27;s browser into a smart light bulb. I&#x27;ll trace the flow through each of these components.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The user chooses a color through a basic web UI written in vanilla HTML, CSS, and JS. There&#x27;s a sketch of the orb that I made in <a href="https://inkscape.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Inkscape</a> to provide a preview.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="559" height="544" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Forb.breq.dev%2Fui.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Forb.breq.dev%2Fui.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Forb.breq.dev%2Fui.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The UI makes calls to a <a href="https://flask.palletsprojects.com/en/stable/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Flask</a>-based backend, which turns around and calls HomeAssistant. This layer exists only to allow unauthenticated users to call these two specific methods on the HomeAssistant API (since the Flask app keeps a bearer token around). I deployed this on <a href="https://dokku.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Dokku</a>, which is a stack <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2021/02/10/dokku">I&#x27;ve developed a strong familiarity with</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I initially tried to make this project work with Google Home, I couldn&#x27;t find an API that would allow me to invoke commands programmatically. Ava and I had been looking for an excuse to migrate to HomeAssistant for a while, and this was it! We found the setup process to be quite straightforward: most of our smart devices are Matter bulbs, which could easily be added to both Google Home and HomeAssistant.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="580" height="349" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Forb.breq.dev%2Fhomeassistant.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Forb.breq.dev%2Fhomeassistant.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Forb.breq.dev%2Fhomeassistant.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">HomeAssistant gives each entity an &quot;Entity ID&quot;, which makes programming easy. The orb light is just <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">lights.orb</code>! While finding the right documentation was a bit difficult, invoking actions on entities through the REST API was overall easy and straightforward.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ve deployed HomeAssistant to a Proxmox server running in <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/minirack">our home mini-rack</a>. Since our HomeAssistant instance is publicly routable at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">homeassistant.home.breq.dev</code>, no special considerations were needed for piping together the Flask app deployed in the cloud with HomeAssistant running in our home network.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In and of itself, the orb demo isn&#x27;t that interesting, especially because you need to actually be in front of the light to see it working (and if you&#x27;re already physically in my apartment, you can just use the Google Home to set the light). That said, it&#x27;s a fun party trick, and I enjoyed finally being able to interact with my smart home devices easily from code! I&#x27;m definitely looking forward to doing more smart home projects in the future.</p></div>]]></description>
            <link>https://breq.dev/projects/orb</link>
            <guid isPermaLink="false">/projects/orb</guid>
            <category><![CDATA[home automation]]></category>
            <category><![CDATA[python]]></category>
            <pubDate>Tue, 29 Apr 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Greatest Hits of the MOS 6502]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Over the years, I&#x27;ve developed a strong appreciation for the MOS 6502, mostly through my work <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2022/11/26/noentiendo">writing an emulator</a> (something I feel <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2024/09/14/emulator">everyone should do</a>). In researching this chip, I&#x27;ve stumbled across countless creative tricks and workarounds both in the design of the chip and the vast library of software written for it.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="zero-page">Zero Page</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This one&#x27;s more of an interesting design choice: the 6502 has an absolutely tiny register file. The only general-purpose register is &quot;A&quot;, the accumulator. There are two additional registers &quot;X&quot; and &quot;Y&quot;, but they are only used for indexing memory.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, the designers of the 6502 realized that this was extremely limiting to programs and would require lots of reads and writes into memory to handle temporary values computed in a program. Using the &quot;Absolute&quot; addressing mode, this requires two bytes for the memory address and around 4 cycles depending on the instruction. The &quot;Zero Page&quot; addressing mode is a hack: when a Zero Page instruction is used, only the lower byte of the memory address is stored, and the processor can skip a cycle! This both reduces the program size and speeds up program execution. As a result, the first 256 bytes of memory are effectively &quot;registers&quot; of sorts!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-cps-pin">The &quot;CPS pin&quot;</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Like many architectures, the 6502 has a status register with flags like &quot;zero&quot;, &quot;overflow&quot;, and &quot;negative&quot; representing the last operation completed with the ALU. Unlike these architectures, though, the 6502 has a fun quirk: the chip has a physical &quot;SO&quot; (&quot;set overflow&quot;) pin which, when the signal on it goes from high to low, sets the overflow flag in the status register. This flag can be used by subsequent branch instructions.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The usefulness of this pin is questionable, and it rarely saw usage in practice. It was apparently used in tight loops for routines interfacing with hardware, since a transition on the pin could cause code to jump out of the loop. The pin <a href="http://www.6502.org/tutorials/vflag.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">was originally called the CPS pin</a>, short for &quot;Chuck Peddle Special&quot; (named after the main designer of the 6502, <a href="https://en.wikipedia.org/wiki/Chuck_Peddle" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Chuck Peddle</a>).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-brk-instruction">The BRK Instruction</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the 6502, opcode <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">00</code> maps to the &quot;BRK&quot; instruction. This is essentially used to trigger an interrupt from within the program.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the 6502, the location in memory that code is executed from after a reset or interrupt is controlled by a table of &quot;vectors&quot; located at the very end of the memory map.</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Signal</th><th class="border border-black p-2 dark:border-white">Location</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">NMI</td><td class="border border-gray-500 p-2">FFFA-FFFB</td></tr><tr><td class="border border-gray-500 p-2">RESET</td><td class="border border-gray-500 p-2">FFFC-FFFD</td></tr><tr><td class="border border-gray-500 p-2">IRQ/BRK</td><td class="border border-gray-500 p-2">FFFE-FFFF</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">NMI and IRQ are for the two pins on the microprocessor of the same name (&quot;non-maskable interrupt&quot; and &quot;interrupt request&quot; respectively). RESET is for when the microcontroller boots up or is reset. BRK shares its vector with IRQ, meaning the code routine which services maskable interrupts is the same routine which services BRK instructions. So, when you run BRK, your code immediately jumps to the interrupt routine. Once the interrupt routine returns with the RTI instruction (&quot;Return From Interrupt&quot;), your code keeps executing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">BRK was <em>intended</em> for use in debugging (as a &quot;breakpoint&quot; of sorts). In some systems, this was what happened: the Apple II would launch the <a href="https://gunkies.org/wiki/Apple_II_Machine_Language_Monitor" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">program monitor</a> which would allow you to debug your program. However, this instruction has a weird quirk: despite BRK being a 1-byte instruction, when the interrupt routine returned, the program counter would jump forward by 2 bytes instead of 1 -- leaving a single byte after the BRK instruction unused. Clever programmers could exploit this by looking at the top of the stack and reading this value, using it to pass state between the code calling BRK and the interrupt service routine.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the BBC Micro, <a href="https://stardot.org.uk/mirrors/www.bbcdocs.com/filebase/essentials/BBC%20Microcomputer%20Advanced%20User%20Guide.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">BRK was used for error handling</a>. To throw an error, the BRK instruction would be called with the error code stored in the byte immediately following it. Since returning from an error handler was not supported, an error string could be stored immediately after the error code byte additionally.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the Apple III, Apple&#x27;s Sophisticated Operating System used <a href="https://apple3.org/Documents/Manuals/Apple%20III%20SOS%20Reference%20Manual%20Volume%202%20-%20The%20SOS%20Calls.PDF" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">BRK for system calls</a>. The system call opcode would be placed immediately after the BRK instruction, then a 2-byte pointer to additional parameters. This did mean the SOS routine responsible for system calls needed to increment the return address by 2.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wozniaks-sweet16">Wozniak&#x27;s SWEET16</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The 6502 is an 8-bit processor, but addresses and other values are often 16 bits. While it&#x27;s of course possible to manipulate these larger values using a combination of 8-bit operations, the resulting code is often quite verbose. Steve Wozniak, known for his obsession with going great lengths to save a small amount of resources, did not want to deal with this, so he created an <em>interpreted bytecode</em> language called <a href="https://archive.org/details/BYTE_Vol_02-11_1977-11_Sweet_16/page/n151/mode/2up" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">SWEET16</a>. A program could call into a subroutine, and the code after the subroutine call would be interpreted as SWEET16 instructions until the SWEET16 &quot;Return&quot; instruction was executed, at which point the following code is executed normally.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">SWEET16 is an interesting tradeoff for code size versus performance. While it massively reduces the size of code, interpreted SWEET16 code is about one tenth the speed of native 6502 code. While optimizing for code size was usually preferred in the past, modern processors have improved slower than modern memory has, so this tradeoff is less relevant now than it once was.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">SWEET16 supported several &quot;nonregister operations&quot;. These included branch operations, which used a 1-byte signed offset as the branch target.</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Opcode</th><th class="border border-black p-2 dark:border-white">Mnemonic</th><th class="border border-black p-2 dark:border-white">Function</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">00</td><td class="border border-gray-500 p-2">RTN</td><td class="border border-gray-500 p-2">Exit SWEET16, return to native 6502 code</td></tr><tr><td class="border border-gray-500 p-2">01</td><td class="border border-gray-500 p-2">BR ea</td><td class="border border-gray-500 p-2">Branch always (i.e., jump)</td></tr><tr><td class="border border-gray-500 p-2">02</td><td class="border border-gray-500 p-2">BNC ea</td><td class="border border-gray-500 p-2">Branch if No Carry</td></tr><tr><td class="border border-gray-500 p-2">03</td><td class="border border-gray-500 p-2">BC ea</td><td class="border border-gray-500 p-2">Branch if Carry</td></tr><tr><td class="border border-gray-500 p-2">04</td><td class="border border-gray-500 p-2">BP ea</td><td class="border border-gray-500 p-2">Branch if Plus</td></tr><tr><td class="border border-gray-500 p-2">05</td><td class="border border-gray-500 p-2">BM ea</td><td class="border border-gray-500 p-2">Branch if Minus</td></tr><tr><td class="border border-gray-500 p-2">06</td><td class="border border-gray-500 p-2">BZ ea</td><td class="border border-gray-500 p-2">Branch if Zero</td></tr><tr><td class="border border-gray-500 p-2">07</td><td class="border border-gray-500 p-2">BNZ ea</td><td class="border border-gray-500 p-2">Branch if Non-Zero</td></tr><tr><td class="border border-gray-500 p-2">08</td><td class="border border-gray-500 p-2">BM1 ea</td><td class="border border-gray-500 p-2">Branch if Minus 1</td></tr><tr><td class="border border-gray-500 p-2">09</td><td class="border border-gray-500 p-2">BNM1 ea</td><td class="border border-gray-500 p-2">Branch if Not Minus 1</td></tr><tr><td class="border border-gray-500 p-2">0A</td><td class="border border-gray-500 p-2">BK</td><td class="border border-gray-500 p-2">Break</td></tr><tr><td class="border border-gray-500 p-2">0B</td><td class="border border-gray-500 p-2">RS</td><td class="border border-gray-500 p-2">Return from Subroutine</td></tr><tr><td class="border border-gray-500 p-2">0C</td><td class="border border-gray-500 p-2">BS ea</td><td class="border border-gray-500 p-2">Branch to Subroutine</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It also featured 16 &quot;registers&quot;, stored in the zero page. These were specified using the second nibble of the opcode, allowing these instructions to take up only a single byte. Notably, some instructions supported an indirect addressing mode. Many operate on register R0, also called the accumulator.</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Opcode</th><th class="border border-black p-2 dark:border-white">Mnemonic</th><th class="border border-black p-2 dark:border-white">Function</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">1n</td><td class="border border-gray-500 p-2">SET Rn $xxxx</td><td class="border border-gray-500 p-2">Set a register to an immediate value</td></tr><tr><td class="border border-gray-500 p-2">2n</td><td class="border border-gray-500 p-2">LD Rn</td><td class="border border-gray-500 p-2">Transfer the value in the specified register to R0</td></tr><tr><td class="border border-gray-500 p-2">3n</td><td class="border border-gray-500 p-2">ST Rn</td><td class="border border-gray-500 p-2">Transfer the value in R0 to the specified register</td></tr><tr><td class="border border-gray-500 p-2">4n</td><td class="border border-gray-500 p-2">LD @Rn</td><td class="border border-gray-500 p-2">Load the low-order byte of R0 from the memory address stored in Rn, then increment the value in Rn</td></tr><tr><td class="border border-gray-500 p-2">5n</td><td class="border border-gray-500 p-2">ST @Rn</td><td class="border border-gray-500 p-2">Store the low-order byte of R0 into the memory address stored in Rn, then increment the value in Rn</td></tr><tr><td class="border border-gray-500 p-2">6n</td><td class="border border-gray-500 p-2">LDD @Rn</td><td class="border border-gray-500 p-2">Load a 16-bit little-endian value from the memory location stored in Rn into R0, then increment the value in Rn by 2</td></tr><tr><td class="border border-gray-500 p-2">7n</td><td class="border border-gray-500 p-2">STD @Rn</td><td class="border border-gray-500 p-2">Store a 16-bit little-endian value from R0 into the memory location stored in Rn, then increment the value in Rn by 2</td></tr><tr><td class="border border-gray-500 p-2">8n</td><td class="border border-gray-500 p-2">POP @Rn</td><td class="border border-gray-500 p-2">Decrement the value in Rn, then load the low-order byte of R0 from the memory address stored in Rn</td></tr><tr><td class="border border-gray-500 p-2">9n</td><td class="border border-gray-500 p-2">STP @Rn</td><td class="border border-gray-500 p-2">Decrement the value in Rn, then store the low-order byte of R0 to the memory address stored in Rn</td></tr><tr><td class="border border-gray-500 p-2">An</td><td class="border border-gray-500 p-2">ADD Rn</td><td class="border border-gray-500 p-2">Add the contents of Rn to R0, and store the result in R0</td></tr><tr><td class="border border-gray-500 p-2">Bn</td><td class="border border-gray-500 p-2">SUB Rn</td><td class="border border-gray-500 p-2">Subtract the contents of Rn from R0, and store the result in R0</td></tr><tr><td class="border border-gray-500 p-2">Cn</td><td class="border border-gray-500 p-2">POPD @Rn</td><td class="border border-gray-500 p-2">Decrement the value in Rn by 2, then load the 16-bit little-endian value from R0 into the memory address stored in Rn</td></tr><tr><td class="border border-gray-500 p-2">Dn</td><td class="border border-gray-500 p-2">CPR Rn</td><td class="border border-gray-500 p-2">Subtract the value in Rn from R0, and set the status flags for branching</td></tr><tr><td class="border border-gray-500 p-2">En</td><td class="border border-gray-500 p-2">INR Rn</td><td class="border border-gray-500 p-2">Increment the contents of Rn</td></tr><tr><td class="border border-gray-500 p-2">Fn</td><td class="border border-gray-500 p-2">DCR Rn</td><td class="border border-gray-500 p-2">Decrement the contents of Rn</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">SWEET16 stands out to me as a shockingly complete interpreted language implemented in a shockingly short amount of code. It&#x27;s something I otherwise wouldn&#x27;t have been expected to be feasible in this context.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="illegal-instructions-and-their-uses">Illegal instructions and their uses</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The 6502 has 151 valid opcodes. However, there are 256 possible values that the first byte of an instruction can take. What happens if you try one of the invalid ones?</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">12 of them just lock up the CPU and prevent it from executing instructions further</li>
<li class="my-2 pl-2">27 of them are no-ops, with varying instruction lengths</li>
<li class="my-2 pl-2">1 has non-deterministic behavior (&quot;XAA&quot;, which I won&#x27;t even try to describe here)</li>
<li class="my-2 pl-2">The rest are perfectly usable, but do some <a href="https://www.pagetable.com/c64ref/6502/?tab=2" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">rather strange operations</a>...</li>
</ul></div>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Mnemonic</th><th class="border border-black p-2 dark:border-white">Description</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">ANC</td><td class="border border-gray-500 p-2">AND memory with accumulator then move negative flag to carry flag</td></tr><tr><td class="border border-gray-500 p-2">ARR</td><td class="border border-gray-500 p-2">AND memory with accumulator then rotate right</td></tr><tr><td class="border border-gray-500 p-2">ASR</td><td class="border border-gray-500 p-2">AND memory with accumulator then logical shift right</td></tr><tr><td class="border border-gray-500 p-2">DCP</td><td class="border border-gray-500 p-2">Decrement memory by 1 then compare with accumulator</td></tr><tr><td class="border border-gray-500 p-2">ISC</td><td class="border border-gray-500 p-2">Increment memory by 1 then subtract from accumulator, store result in accumulator</td></tr><tr><td class="border border-gray-500 p-2">LAS</td><td class="border border-gray-500 p-2">AND memory with stack pointer, store result in accumulator, X register, and stack pointer</td></tr><tr><td class="border border-gray-500 p-2">LAX</td><td class="border border-gray-500 p-2">Load both accumulator and index register from memory</td></tr><tr><td class="border border-gray-500 p-2">RLA</td><td class="border border-gray-500 p-2">Rotate memory left then AND with accumulator</td></tr><tr><td class="border border-gray-500 p-2">RRA</td><td class="border border-gray-500 p-2">Rotate memory right then add to accumulator</td></tr><tr><td class="border border-gray-500 p-2">SAX</td><td class="border border-gray-500 p-2">Store accumulator AND X register into memory</td></tr><tr><td class="border border-gray-500 p-2">SBX</td><td class="border border-gray-500 p-2">Subtract memory from accumulator AND X register, store in X</td></tr><tr><td class="border border-gray-500 p-2">SHA</td><td class="border border-gray-500 p-2">Store accumulator AND index register AND a value dependent on the addressing mode into memory</td></tr><tr><td class="border border-gray-500 p-2">SHS</td><td class="border border-gray-500 p-2">AND accumulator with X register, store in stack pointer, then store stack pointer AND high byte of memory address into memory</td></tr><tr><td class="border border-gray-500 p-2">SHX</td><td class="border border-gray-500 p-2">Store index register X AND upper byte of address plus 1 into memory</td></tr><tr><td class="border border-gray-500 p-2">SHY</td><td class="border border-gray-500 p-2">Store index register Y AND upper byte of address plus 1 into memory</td></tr><tr><td class="border border-gray-500 p-2">SLO</td><td class="border border-gray-500 p-2">Shift memory left, then store memory OR accumulator into accumulator</td></tr><tr><td class="border border-gray-500 p-2">SRE</td><td class="border border-gray-500 p-2">Shift memory right, then store memory XOR accumulator into accumulator</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The technical reasons for why this behavior happens are described well in <a href="https://www.masswerk.at/nowgobang/2021/6502-illegal-opcodes" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this blog post</a> and <a href="https://codebase64.org/lib/exe/fetch.php?media=base:nomoresecrets-nmos6510unintendedopcodes-20172412.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this document</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few of these instructions were used in <a href="https://www.nesdev.org/wiki/CPU_unofficial_opcodes#Games_using_unofficial_opcodes" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">a few NES games</a> (mostly 2-byte NOPs). These also saw use on various <a href="https://github.com/mattgodbolt/jsbeeb/issues/5" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">BBC Micro games</a>. Games used these for both copy protection (as an attempt to validate the hardware?) and for performance. My guess is that more serious software doesn&#x27;t bother with these since performance is less of a concern than correctness.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Later revisions of the 6502, such as the <a href="https://www.westerndesigncenter.com/wdc/documentation/w65c02s.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">W65C02S</a>, replaced the opcodes in the &quot;F&quot; column with additional bit manipulation opcodes.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-ricoh-2a032a07-and-the-nes">The Ricoh 2A03/2A07 and the NES</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The 6502 was incredibly popular, and inspired a few clones and derivatives. One such clone was the Ricoh 2A03/2A07 chip used in the Nintendo Entertainment System. Apparently, Nintendo was not a big believer in copyright law back in this era. Oh, how the times have changed...</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To see how blatant this was, first look at this image of the 6502 chip (courtesy of <a href="http://www.visual6502.org/images/6502/index.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Visual 6502</a>)...</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4677" height="5097" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2F6502%2F6502_top_op10x_BF_4677.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2F6502%2F6502_top_op10x_BF_4677.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, look at this image of the Ricoh 2A03 (also from <a href="http://www.visual6502.org/images/pages/Nintendo_RP2A_die_shots.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Visual 6502</a>)</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1600" height="1573" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2F6502%2FNintendo_RP2A03G_die_shot_1a_1600w.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2F6502%2FNintendo_RP2A03G_die_shot_1a_1600w.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2F6502%2FNintendo_RP2A03G_die_shot_1a_1600w.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Does that little bit in the lower right corner look familiar?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Interestingly, the Binary Coded Decimal (BCD) functionality of the 6502 were fused off before the chip was incorporated into the Ricoh clone. This feature allowed the 6502 to perform arithmetic operations on numbers stored as decimal, with 4 bits representing a decimal digit. It was covered under a <a href="https://patents.google.com/patent/US3991307" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">patent held by MOS</a>, which is probably why Ricoh disabled it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The other parts of this chip are for things like the NES&#x27;s sound generator, known as the &quot;APU&quot; and definitely worthy of its own post someday. It&#x27;s also why there are two variants of this chip: one for NTSC and one for PAL.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The 6502 is one of the final bastions of an age where microprocessors could be contained in a pile of parchment in a dude named Chuck&#x27;s desk. It was one of the first mainframes in a chip, and powered countless number of people&#x27;s first experiences in computing. The number of programmers today who got their start on the 6502 alone makes the chip worldchanging in it of itself.</p></div>]]></description>
            <link>https://breq.dev/2025/04/23/6502</link>
            <guid isPermaLink="false">/2025/04/23/6502</guid>
            <category><![CDATA[retro]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[A "5V Bypass Mod" for the PI040202-7X2C PCIe Card]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">The article below was republished from the internal <a href="https://www.northeasternrover.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NURover</a> Notion wiki. On the Rover team, we use a PCIe to USB card with four individual USB controllers to provide enough bandwidth for our camera streaming system (something I&#x27;ve <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2023/06/21/cameras">posted about previously</a>). As you can probably imagine, the USB system on the rover has very high current draw, and we&#x27;ve fried cards (quite dramatically) in the past.</p>
<div class="mx-auto my-8 w-full max-w-xl border border-black"></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Some PCIe to USB cards require a modification to pass through a high amount of current on the 5V rail. This page describes how to apply the modification.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="do-i-need-to-perform-a-5v-bypass-mod">Do I need to perform a 5V Bypass Mod?</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>There are two types of &quot;quad chip&quot; PCIe card available</strong>: “B” variant and “C” variant.</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="1461" height="712" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe085f25e-cfcc-45a3-9550-c0660acb77dd.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe085f25e-cfcc-45a3-9550-c0660acb77dd.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe085f25e-cfcc-45a3-9550-c0660acb77dd.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">PI040202-7X2B (”B” variant)</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="1423" height="653" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F0f0c30a1-d1f2-443f-99b4-86e812f627fc.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F0f0c30a1-d1f2-443f-99b4-86e812f627fc.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F0f0c30a1-d1f2-443f-99b4-86e812f627fc.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">PI040202-7X2C (”C” variant)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>This modification is only required for the “C” variant.</strong> The “B” variant boards provide power from the +5V pin of the Molex or SATA power connector directly to the USB port (after some LC filtering). The “C” variant boards instead provide power from the +12V pin on the power connector, passed through a voltage regulator chip. This chip cannot handle the high current loads present on the rover.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Once performing this modification, the card can no longer be powered from the PCIe slot and will REQUIRE external power to operate.</strong></p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="removing-the-voltage-regulators">Removing the Voltage Regulators</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first step is to remove the 12V to 5V voltage regulator chips. There are two ways to do this.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hot-air">Hot Air</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The least destructive method of removal is the use of a hot air workstation.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Set the hot air gun to a temperature of approximately 450 F and turn the fan up.</li>
<li class="my-2 pl-2">Apply heat to the chip.</li>
<li class="my-2 pl-2">Once the chip is heated, use a small flathead screwdriver to push it out of position and away from its footprint.</li>
</ul></div>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="1061" height="1057" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe160f4a6-aff7-40b0-90f8-bcbe4287a7f3.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe160f4a6-aff7-40b0-90f8-bcbe4287a7f3.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fe160f4a6-aff7-40b0-90f8-bcbe4287a7f3.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Notes on this method:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Note that if you do not push the chip completely off of the footprint, it may reattach to a different set of pads. Just try again!</li>
<li class="my-2 pl-2">It is okay if the components surrounding the chip start to move a bit — the soldermask and the surface tension of the solder should keep them roughly in place. Even if knocked off, components are for filtering the <em>input</em> side of the voltage regulator and are thus not needed.</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cutting-tools">Cutting Tools</h3>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="642" height="542" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F7367cbf9-aac6-4855-963c-e78b7c46d75e.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F7367cbf9-aac6-4855-963c-e78b7c46d75e.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F7367cbf9-aac6-4855-963c-e78b7c46d75e.png&amp;w=1920&amp;q=75"/></div>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="570" height="519" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F5b5779b6-8259-4ca8-a22a-19fed2c2ea5a.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F5b5779b6-8259-4ca8-a22a-19fed2c2ea5a.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F5b5779b6-8259-4ca8-a22a-19fed2c2ea5a.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If the chip is burnt, you can use flush cutters to remove it.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Remove the plastic casing of the chip by scraping it off.</li>
<li class="my-2 pl-2">Using flush cutters, cut the legs of the chip off.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Notes on this method:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Make sure to avoid lifting pads! If the pads are lifted from the board too much, they can flop around and touch each other and cause shorts. Trim the pads as far back as possible.</li>
<li class="my-2 pl-2">This method gives you much less room between the pads of the chip and the side of the inductor you want to solder to, which makes soldering more difficult.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="attaching-jumpers">Attaching Jumpers</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You want to bridge the +5V input from the Molex connector to the two +5V outputs that would have been coming from the regulator.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Your first instinct may be to solder to the 5V output pad where the regulator used to be. This is possible, but difficult to do by hand. A much easier approach is to solder to the input side of the inductor!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Drop a blob of solder on the side of the inductor. The picture shows slightly too much, I cleaned it up after. Don’t worry too much about making it pretty.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cut a small length of wire (I used 24 gauge) and strip a bit at the end. Tin the end with more solder.</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="535" height="504" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F21e3f9cc-cd1b-4498-b919-9fa7019b4993.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F21e3f9cc-cd1b-4498-b919-9fa7019b4993.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F21e3f9cc-cd1b-4498-b919-9fa7019b4993.png&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, use one hand to push the wire into position and the other hand with the soldering iron to heat the pad. It may take a while for the inductor to get up to temperature — be patient!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The reason we were liberal with solder before is it frees up one of your hands — you don’t need to hold the solder while attaching the wire.</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="852" height="889" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F8c7e1d77-6e32-4a8d-b544-5ae997b00e72.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F8c7e1d77-6e32-4a8d-b544-5ae997b00e72.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F8c7e1d77-6e32-4a8d-b544-5ae997b00e72.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Repeat the process for the second inductor, attaching a second wire.</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="1067" height="986" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F234fe823-d364-4704-8939-986ff1efcdbb.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F234fe823-d364-4704-8939-986ff1efcdbb.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2F234fe823-d364-4704-8939-986ff1efcdbb.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Trim the wires to length — you want them to attach to the +5V pin on the Molex connector (furthest from the PCIe connector on the card). You can line them up perpendicular style like so to attach both easily.</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="2076" height="1564" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fimage1.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fimage1.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, add solder!</p>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="2076" height="1564" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fimage2.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fimage2.png&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="testing-for-shorts">Testing for Shorts</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are two shorts that you need to check for the presence for. The GND and 12V pins heading to where the voltage regulator was are very close to the pad on the inductor that you soldered to, and bridging is very possible. You can test this by checking connectivity between your jumper wires and the USB connector shielding (for GND) and between your jumper wires and the <strong>output side of diodes D1/D2 and D3/D4</strong> (for 12V).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If neither rail is shorted, your board is ready to use!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="schematic">Schematic</h2>
<div class="contents break-inside-avoid print:block"><img alt="image.png" loading="lazy" width="1336" height="1513" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fff1e0c1b-6bbe-4190-a8d2-5c2ee137775c.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fff1e0c1b-6bbe-4190-a8d2-5c2ee137775c.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpcie-usb-5v-bypass%2Fff1e0c1b-6bbe-4190-a8d2-5c2ee137775c.png&amp;w=3840&amp;q=75"/></div></div>]]></description>
            <link>https://breq.dev/2025/04/09/usb-pcie-5v-bypass</link>
            <guid isPermaLink="false">/2025/04/09/usb-pcie-5v-bypass</guid>
            <category><![CDATA[hardware]]></category>
            <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[KiCAD SVG Prettifier]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3056" height="1466" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fkicad-pretty%2Fkicad-pretty.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fkicad-pretty%2Fkicad-pretty.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project recolors SVG exports of KiCAD circuit board designs to appear in realistic colors.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I faced an annoying problem with KiCAD the other day. I wanted to export a realistic-looking image of a PCB design to use for an upcoming project. However, the existing options for doing this didn&#x27;t fit my needs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The 3D viewer, while very cool, isn&#x27;t great for this use case. I would prefer a vector image to a raster one. Plus, with the way the lighting is set up, an &quot;exactly head-on view&quot; of a board can look very blown out. Here&#x27;s a board with light-colored silkscreen as viewed head-on in the 3D viewer:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1730" height="1256" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fkicad-pretty%2Ftoo-bright.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fkicad-pretty%2Ftoo-bright.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fkicad-pretty%2Ftoo-bright.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">KiCAD also has the ability to export an SVG natively, but the result looks like what you would see in the editor: the colors match the editing layers, not the actual realistic colors of the board, and the soldermask layer is inverted. Plus, I wanted a way to generate separate front and back views, with the back view mirrored to how it would appear in real life.</p>
<img class="mx-auto" src="/images/kicad-pretty/kicad-svg.svg" alt="" width="1123" height="794"/>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="implementation">Implementation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, I built a tool to recolor the output from the KiCAD format to look more realistic. Annoyingly, KiCAD-generated SVGs do not put each layer into a separate SVG group or layer, so my code queries for nodes based on their existing color to apply the new colors. This would break if KiCAD exports things in an unexpected color, but since these colors are <a href="https://forum.kicad.info/t/colors-in-svg-export/43668" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">seemingly not configurable by the user</a>, this should be fine.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I chose to write this app in JavaScript since the language already provides good utilities for parsing, querying, and manipulating XML documents like SVG files. The UI is pretty utilitarian, but hey, it works and loads quickly! I wrote this without using a framework for styling or JavaScript -- it was nice to take a break from the complex tooling of my usual React/Tailwind setup and go back to basics for a bit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The whole app is based around some <em>gnarly</em> CSS selectors, especially since the format changes substantially between KiCAD versions and I was trying to target versions 7 through 9.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here are some example outputs for a board I&#x27;m working on:</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><img class="mx-auto" src="/images/kicad-pretty/front.svg" alt="" width="1123" height="794"/> <br/> <img class="mx-auto" src="/images/kicad-pretty/back.svg" alt="" width="1123" height="794"/></p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="try-it-yourself">Try it yourself</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you want to try it out yourself, you can! First, in KiCAD, export an SVG with the relevant layers. In older versions, this is straightforward:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1508" height="1316" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fkicad-pretty%2Fkicad-export.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fkicad-pretty%2Fkicad-export.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fkicad-pretty%2Fkicad-export.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, in the new version, they&#x27;ve replaced the &quot;Export SVG&quot; dialog with the &quot;Plot&quot; window, which exports a separate SVG file for each layer (which is not what we want!) The easiest way to get a single SVG image is with the KiCAD CLI (replace <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">MyBoard.kicad_pcb</code> with the filename of your board file):</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">kicad-cli pcb </span><span class="" style="color:#404040">export</span><span class=""> svg --mode-single --layers F.Cu,B.Cu,F.Silkscreen,B.Silkscreen,F.Mask,B.Mask,Edge.Cuts MyBoard.kicad_pcb</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, go to <a href="https://kicad-pretty.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><strong>kicad-pretty.breq.dev</strong></a> and:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">Upload your SVG</li>
<li class="my-2 pl-2">Change the colors as desired (e.g. if you use a different soldermask color)</li>
<li class="my-2 pl-2">Click &quot;Make Pretty!&quot;</li>
<li class="my-2 pl-2">View each preview with the &quot;Show Front&quot; and &quot;Show Back&quot; buttons</li>
<li class="my-2 pl-2">Export each side with the &quot;Export&quot; button</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re having issues with the export format, feel free to reach out to me and I&#x27;ll do my best to investigate! I&#x27;ve only been able to try a handful of SVG viewers and KiCAD boards so far.</p></div>]]></description>
            <link>https://breq.dev/projects/kicad-pretty</link>
            <guid isPermaLink="false">/projects/kicad-pretty</guid>
            <category><![CDATA[web]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Who's Using breq.dev?]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">My friend Jules made a fun suggestion on Bluesky recently:</p>
<div class="mx-auto max-w-lg"><blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:ra3gxl2udc22odfbvcfslcn3/app.bsky.feed.post/3lk25eplens2o" data-bluesky-cid="bafyreiebisw5cbd42fmftq7tmmljyxuczmlvyo6h6xpqb3wqsfij76xqlm" data-bluesky-embed-color-mode="system"></blockquote></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s step through the <a href="https://github.com/search?q=breq.dev&amp;type=code" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">search results</a> for my domain name on GitHub and see what we can find!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="eightyeightthirtyone">eightyeightthirty.one</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In 2023, some friends and I built a scraper tool called <a href="https://eightyeightthirty.one/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">eightyeightthirty.one</a> which attempted to map the entire graph of 88x31 buttons. Soon after, I wrote an article <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2023/12/26/88x31-science">applying network science to the 88x31 graph</a>.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://tantek.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tantek Çelik</a>, the Web Standards Lead at Mozilla, liked my article enough to add it to the <a href="https://indieweb.org/88x31" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">IndieWiki page on 88x31s</a>!</li>
<li class="my-2 pl-2"><a href="https://sylkos.xyz/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Julia Keadey</a> discussed the same article in a <a href="https://sylkos.xyz/blog/0" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">blog post about her website design</a>.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few people credited my project page for the 88x31 scraper tool. Please credit <a href="https://notnite.com/portfolio#eightyeightthirtyone" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NotNite&#x27;s portfolio</a> instead, she did most of the work!</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://sitoctt.octt.eu.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">sitoctt</code></a>&#x27;s <a href="https://sitoctt.octt.eu.org/it/miscellanea/%EF%B8%8F-Raccolta-Emblemi/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">blog post</a></li>
<li class="my-2 pl-2"><a href="https://dongdigua.github.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dongdigua</code></a>&#x27;s <a href="https://dongdigua.github.io/internet_collections" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;Internet Collections&quot;</a> page</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="5f3759df">5F3759DF</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few people have linked to my 2021 article on the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2021/03/17/5F3759DF">fast inverse square root algorithm</a> in code over the years:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">kvn13github</code>&#x27;s <a href="https://github.com/kvn13github/0x5F3759DF-in-Python3-FastInverseSquareRoot/blob/main/FastInverseSquareRoot.py" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Python implementation</a></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">silvernuke911</code>&#x27;s <a href="https://github.com/silvernuke911/Random-programs/blob/main/Fast_Inverse_Square_Root.py" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Python implementation</a></li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="flowspace">Flowspace</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At least one person other than myself has set up a profile on <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/flowspace">Flowspace</a>, a social network demo I made in 2021.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">R47n</code>&#x27;s <a href="https://r74n.com/social/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">website</a> links to their <a href="https://flowspace.breq.dev/profile/525884243050905603" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Flowspace profile</a>.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="accelcoin">Accelcoin</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In 2022 I was a teaching assistant for Fundamentals of Computer Science 1 at Northeastern. One of our course assignments was for students to build a working blockchain node implementation. I ran some infrastructure relating to this! Some students have published their code to GitHub.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://github.com/Shreypatel2224/BlockchainProject" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Shreypatel2224</code></a></li>
<li class="my-2 pl-2"><a href="https://github.com/nishmistry/Accelcoin" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">nishmistry</code></a></li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wordle">Wordle</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In 2022 I published a few <a href="https://breq.dev/projects/wordle" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">implementations of the Wordle algorithm</a> in TypeScript and Rust.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://turtywurty.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">turtywurty</code></a>&#x27;s discord bot <a href="https://github.com/DaRealTurtyWurty/SuperTurtyBot" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">SuperTurtyBot</a> includes Wordle functionality, <a href="https://github.com/DaRealTurtyWurty/SuperTurtyBot/blob/d5e1499647a5eae15a596de73a57e522a175c4a8/src/main/java/dev/darealturtywurty/superturtybot/commands/minigames/WordleCommand.java#L686" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ported to Java</a> from my implementation.</li>
</ul></div></div>]]></description>
            <link>https://breq.dev/2025/03/10/whos-using-breqdev</link>
            <guid isPermaLink="false">/2025/03/10/whos-using-breqdev</guid>
            <category><![CDATA[website]]></category>
            <pubDate>Mon, 10 Mar 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Reflections on Four Years of Rover]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">As many of you may know, I&#x27;ve been involved with the <a href="https://www.northeasternrover.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Northeastern University Mars Rover Team</a> for four years now. We compete each year in the <a href="https://urc.marssociety.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">University Rover Challenge</a> and occasionally the <a href="https://circ.cstag.ca/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Canadian International Rover Challenge</a>. I personally have competed in four URC events and one CIRC event, and will (hopefully!) be competing at the upcoming URC and CIRC events this summer.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s our latest System Acceptance Review video, which gives some context for the team structure and the subsystems we develop.</p>
<div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/9GoSA4WFsdQ/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/9GoSA4WFsdQ/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Rover has largely dominated my college experience. Late nights in the lab have been a staple of all four years of my college life. During my co-op searches, rover has consistently been the only thing any interviewer has ever wanted to talk to me about. While the club has occasionally gotten overwhelming, I have no regrets about pouring as much energy and effort into the team as I have.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This post is a collection of things I learned from my time on the team and reasons why experiences like this are valuable. This section is written from my perspective as a software developer, but the general principles apply across the engineering disciplines.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="thinking-across-disciplines">Thinking across disciplines</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s critical to work with engineers outside the software field, for two reasons: it helps you teach others about software, and it helps you learn about other forms of engineering.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As an early career software developer, you might never work with anyone outside the software field unless you seek those situations out. Even if classes require team assignments, they are typically done exclusively by students majoring in computer science. Furthermore, many internships on large software teams don&#x27;t involve any communication with those outside the software team. During my 6-month co-op at Amazon Robotics, I can&#x27;t point to a single issue in which I had to work deeply with someone with expertise outside of embedded software in order to solve a technical problem. That&#x27;s a shame, since communicating across disciplines is a critical skill for solving problems in the real world.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The other advantage of an experience like the rover team is that it allows you to develop an understanding of fields outside your core strengths. While on rover, I did electrical work, replaced and installed mechanical parts, and even got involved in our filming team for our System Acceptance Review video. My greatest regret on the team is not learning SolidWorks. The base level understanding of electrical and mechanical engineering I developed has come in handy countless times over the years.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hands-on-experience">Hands-on experience</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My university is (somewhat) unique in that it encourages students to participate in 6-month &quot;co-ops&quot; instead of 4-month internships. They pride themselves on this &quot;experiential learning&quot; approach, and one could assume that it diminishes the importance of developing hands-on experience through clubs. However, I would argue the opposite: co-op program participants benefit greatly from hands-on club experience at least as much as those on a traditional internship schedule.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One reason is that club experiences give members a much broader range of fields and topics to study. Co-op projects are often small, self-contained, and confined to a small field and area of the product. In clubs like the rover team, you can choose as many or as few topics as you like to learn about and dive deep into. Clubs like the rover team have countless subsystems and components, both hardware and software, which need to be integrated; with members leaving every four years, there are plenty of opportunities to become the team expert in a particular system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">During my time on the rover team, I became the &quot;expert&quot; on our command and control interface (since rewriting it happened to be my first project), our radio system (since I was in the right place at the right time to troubleshoot it at my first competition), our camera streaming system (since I arranged to take over development on it after the previous experts graduated), and our migration to ROS 2 (since I was in a leadership position at the time we migrated). Unless you work at an early-stage startup, it&#x27;s difficult to develop this breadth of expertise in a corporate environment.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Similarly, engineering clubs allow members to be part of high-level design decisions and planning steps that are often opaque to interns and co-op students. The design of large-scale software systems is a critical skill often not taught well in classes. You can study design patterns all day long, but putting that knowledge into practice is a skill that takes, well, practice.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another advantage of club experiences that it teaches you software development practices <em>before</em> you go on an internship or co-op. I am always amazed at the number of extremely smart Northeastern students who have no experience with Git, pull request workflows, how to leave a code review, or other critical steps in the software development process until their first co-op. While Northeastern does have a class which covers these, it is taught too late and in too little detail. While you could make the argument that teaching these skills is the purpose of a co-op, it often doesn&#x27;t work like that in practice: small companies may employ poor software development practices, large companies like Google or Amazon use their own bespoke workflows unlike others in the industry, and students don&#x27;t always get the chance to leave code reviews themselves.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="leadership-and-collaboration">Leadership and collaboration</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Working on a 4 person team is quite different from working on a 40 person team, and a 40 person team is quite different from a 400 person team. My rover team had around 40 active members at a given time. I think this number is about perfect for a few reasons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">It&#x27;s a large enough group of people that a hierarchical team structure is necessary to be productive. Members need to be split into subteams, subteams need leads, and subteam leads need a team lead. No one person can hold all of the expertise about the system in their head at any given time.</li>
<li class="my-2 pl-2">It&#x27;s a small enough group of people that becoming the expert in a subsystem or rising into a leadership position is very possible for all incoming members.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m sure I&#x27;m far from the first person to point out that personal projects are no substitute for building things with others, but it&#x27;s true! Going directly from personal work to working 40 hours a week on a team is a jarring transition for many. Although software courses try to remedy this with teamwork assignments, there is no substitute for the experience gained working with a large team over multiple years.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="its-fun">It&#x27;s fun!</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My final reason is that rover development work and competition is fun as hell. If the only software development work I did was for classes or co-op, I worry I would lose interest quickly in the field. Personal projects can be fun to develop as well, but nothing compares to the shared sense of comradery, triumph, and accomplishment I experienced on the rover team.</p></div>]]></description>
            <link>https://breq.dev/2025/03/06/rover</link>
            <guid isPermaLink="false">/2025/03/06/rover</guid>
            <category><![CDATA[software]]></category>
            <category><![CDATA[rover]]></category>
            <category><![CDATA[urc]]></category>
            <pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Wall Matrix 2]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1912" height="1912" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Ftrains_are_sleeping.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmatrix2%2Ftrains_are_sleeping.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Ftrains_are_sleeping.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project is a wall-mounted LED matrix built to show album art, nearby transit and bikeshare information, and weather data.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Almost four years ago, I made the first iteration of a <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">wall-mounted LED matrix</a> project to show weather and transit info, and it&#x27;s one of the projects I&#x27;ve gotten by far the most use out of. It&#x27;s been hung at my childhood home in Maine, in various dorm rooms over my college years, and now at my apartment I share with <a href="https://avasilver.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a>.</p>
<div class="flex flex-col md:flex-row gap-2 [&amp;&gt;img]:!aspect-square [&amp;&gt;img]:!object-cover"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="713" height="713" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=1920&amp;q=75"/></div> <div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="699" height="700" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=1920&amp;q=75"/></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Over that time, we&#x27;ve made only a few changes:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The display now automatically cycles between weather and transit info (since I like to see both before commuting).</li>
<li class="my-2 pl-2">Weather info now displays in both Fahrenheit and Celsius (since Ava prefers the latter).</li>
<li class="my-2 pl-2">Multiple upcoming trains are shown instead of just one.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, as we&#x27;ve added each of these features, we&#x27;ve needed to pack more information into a very small (only 16x32!) screen. This tips the balance away from legibility (mostly fine for us, but less useful for guests at our house).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Simultaneously to this, the two of us got a record player shortly after moving in and have been starting to build a record collection, which has reinvigorated our appreciation of album art. I thought the idea of a piece of wall art to display album covers sounded cool, so I started looking at getting a variant of the LED matrix with a square aspect ratio.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">&quot;Hardware Rev 2&quot; is built around a 64x64 matrix panel with half the pixel pitch of the original, leading to a device with the same width and double the height. I decided to build it with a Raspberry Pi Zero 2W and a <a href="https://www.adafruit.com/product/3211" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Matrix Bonnet</a> from Adafruit with matching dimensions to shrink the electronics down from the original and let it sit closer to the wall.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This also meant that I could build this new revision without taking parts from the old build! I gave the old model to my girlfriend <a href="https://miakizz.quest" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="software">Software</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also chose to start from scratch with the software of this version. I&#x27;ve learned a lot about software development since writing the first iteration of the code, and the old design included way too many layers of abstraction. (Features like the preview display and web server were cool, but saw little day-to-day use.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The display supports four main screens with information pulled from Spotify, weather, MBTA, and BlueBikes.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2596" height="2596" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Fspotify.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Fspotify.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Spotify screen just displays the artwork from whichever album Ava or I am listening to at a given moment. While I was initially happy to see Spotify&#x27;s CDN making a perfectly-sized 64x64 version of the image available, it seems to be too affected by JPEG artifacts to be usable here. So, I settle for downscaling a higher-resolution version.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Spotify data is pulled using the <a href="https://spotipy.readthedocs.io/en/2.25.0/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">spotipy</a> library.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2351" height="2351" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Fweather.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Fweather.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The weather screen is pretty similar to the weather screen on the old version, showing an icon from <a href="https://dhole.github.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Dhole</a>&#x27;s awesome <a href="https://github.com/Dhole/weather-pixel-icons/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">weather pixel icons</a> alongside the current time, temperature, and high/low temp for the day. (Thankfully, we now have labels corresponding to the F and C temperatures.) Data is sourced from <a href="https://openweathermap.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OpenWeatherMap</a>, using a lookup table I made to match their weather condition codes to weather pixel icons.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2312" height="2312" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Fmbta.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Fmbta.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The MBTA screen is designed to mimic a station countdown clock, but with data pulled from several bus and train lines near my apartment. It merges schedules and realtime predictions from the <a href="https://api-v3.mbta.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MBTA Realtime API</a>. I&#x27;ve written some custom logic to try to deduplicate schedule and realtime data based on trip identifiers, but it doesn&#x27;t always work perfectly.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2200" height="2200" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Fbikes.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Fbikes.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The BlueBikes screen is probably the one I&#x27;m most proud of. I hand-drew each of the icons for bikes, e-bikes, and docks that show for each station.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bikeshare services publish realtime using the <a href="https://gbfs.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">General Bikeshare Feed Specification</a> format (GBFS), analogous to the <a href="https://gtfs.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GTFS</a> format used for transit data. The BlueBikes API has excellent <a href="https://bluebikes.com/system-data" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">documentation</a>. It&#x27;s a bit inefficient (there&#x27;s no way to filter to just a subset of stations that I&#x27;ve found yet), but it works great for this use case.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="256" height="256" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Ffish.png&amp;w=256&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmatrix2%2Ffish.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Ffish.png&amp;w=640&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friends and I have fun with a website called <a href="http://makea.fish/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">makea.fish</a>, which generates an image of a fish at 11:11. (We&#x27;ve worked on various other <a href="https://directory.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">fish generators</a>, too!)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As a fun easter egg, the LED matrix will switch at 11:11 to showing a fish image generated by makea.fish which refreshes every 10 seconds.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hardware">Hardware</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2931" height="2931" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmatrix2%2Finside.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmatrix2%2Finside.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A view of the back of the device, showing the 3M command strips used to hang it on the wall</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Just like the original, this LED matrix sign is contained within a 3D-printed enclosure. It uses a Raspberry Pi Zero 2W (my first project with a Pi Zero!) with Adafruit&#x27;s <a href="https://www.adafruit.com/product/3211" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Matrix Bonnet</a>, using a DC jack &quot;extender&quot; I soldered to route the power input to the bottom of the device.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The case features a &quot;chin&quot;, since the stackup height of the electronics is annoyingly tall. I also added ventilation holes, since there was a bit of discoloration on the wall from the old design. This was my first 3D printing project in a while (outside of <a href="https://www.quadratic3d.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">my day job</a> working with volumetric printing).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Quite a long time ago (was it middle school??), I bought a <a href="https://www.mpselectmini.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Monoprice Select Mini</a> 3D printer. It held up surprisingly well over the years, including through two hotend replacements. This is the printer that the old LED sign case was printed with! It had a print volume of only 120x120x120mm, so I had to break up the piece into two separate parts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few years ago, I bought an Ender 3 Pro during a Micro Center sale. (Annoyingly, I was living away from Boston over the summer, so I ended up grabbing it and lugging it around for a full day of sightseeing with my parents -- in my defense, the sale was about to expire!) I set it up and ran a few prints, but could seemingly never get it to work as well as my old Monoprice one. While I used this for the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/outshine">rave choker</a> project, it just didn&#x27;t work well for printing larger parts. It spent a few years collecting dust at my parents&#x27; house.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, earlier this year, I decided to take the printer back down to my apartment and try to get it up and running again. I ended up doing almost a full rebuild, and found <em>just two loose screws</em> holding the Z-axis stepper in place (maybe vibrated out, maybe not tightened properly in the first place). After tightening those, the parts for this project came out beautifully!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Ender 3 is large enough to fit the full size of the LED matrix, but isn&#x27;t quite big enough to print the entire case in one piece. Thus, I broke the part up into the frame around the LED matrix and the &quot;chin&quot; below which contains the Raspberry Pi. The two parts are attached using heat-set inserts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And as for why the parts are pink instead of black: after years of being stored badly, the pink spool was one of the only ones I had which wasn&#x27;t made too brittle by the humidity. (Plus, the pink and black scheme is growing on me...)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m really happy with this! It fits nicely into our living space and does its job of both looking unique and providing useful info. I have thought of a few improvements to make, which will maybe happen at some point. There&#x27;s a lot of empty space in the enclosure right now!</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Automatic brightness adjustment</strong>: The sign easily lights up a dark room. I keep it out in the living room instead of the bedroom, so it&#x27;s not a big deal, but it&#x27;d still be a nice touch.</li>
<li class="my-2 pl-2"><strong>On-device controls</strong>: It would be nice to be able to configure the device, change modes, turn on/off, etc. with some interface on the device itself. While the display could be reused for an on-screen menu system, we&#x27;d need some form of button for user input. It would definitely need to be minimal (I don&#x27;t want this to turn into a wall-mounted gameboy). I think an encoder with pushbutton might be a nice touch, since the only thing visible to the outside would be a single knob.</li>
<li class="my-2 pl-2"><strong>Cloud interaction</strong>: The old matrix had a website where anyone could enter a message and it would scroll across the screen. I haven&#x27;t added anything like this to the new code (yet), but it&#x27;d be pretty fun! (Maybe it could even show drawings using the increased resolution...)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It also feels quite polished compared to a lot of the quick builds I&#x27;ve done, like I could do a small run of these. The parts costs are a bit high (mostly the LED matrix itself), so that&#x27;ll probably never happen, but I can dream!</p></div>]]></description>
            <link>https://breq.dev/projects/matrix2</link>
            <guid isPermaLink="false">/projects/matrix2</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Why Amateur Radio?]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">The amateur radio hobby is generally dominated by a particular demographic. If you look around at a typical large hamfest, spend any time listening in on a net, or ask anyone for their stories of family members, you&#x27;ll easily spot it. As a young queer student, my friends and family members are often surprised to hear that I&#x27;m involved in the community. So why do I gravitate to it?</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="projects">Projects</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As my software knowledge has matured, many of my recent projects have been more focused on building things that I would find useful in my day-to-day life. Amateur radio is a great opportunity for projects like those. Radio hardware is often very open to hacking and experimentation, open-source custom firmware and custom programming tools are extremely common.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Personally, I enjoy connecting the hobby to my interest in software through projects like <a href="https://rolodex.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">rolodex</a>, a &quot;contacts app&quot; for storing callsigns and repeater information, or my work with the <a href="https://www.northeasternrover.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Northeastern Mars Rover Team</a> involving autonomous mission control over <a href="https://en.wikipedia.org/wiki/AX.25" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AX.25</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="adventure-and-practicality">Adventure and Practicality</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When amateur enthusiasts talk about &quot;off-grid communication,&quot; it&#x27;s often in reference to an emergency scenario in which cellular networks fail. While amateur radio does often prove useful in these scenarios, it&#x27;s infrequent enough that I don&#x27;t find it particularly exciting.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another situation where off-grid communication is desirable is in those few areas which are still unreachable by cell signal. I&#x27;ve personally experienced the usefulness of this at the <a href="https://urc.marssociety.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">University Rover Challenge</a>, where the site has little to no cell coverage and the range of cheap FRS radios is often inadequate. I&#x27;ve also tuned my handheld radio to weather frequencies to get forecast information in remote areas of Utah and New Mexico.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Even in situations where communicating over cell phones is possible, radio can still pose advantages. It&#x27;s much easier to communicate via radio while driving a car, while outside with gloves on, or in other situations where reaching for a phone is inconvenient.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="train-autism">Train autism</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of my favorite things to use my handheld radio for is scanning: listening to signals on railroad channels, marine channels, and other government or commercial bands. Listening to how radio is used in the world around me illuminates systems and processes like train dispatch, ship-to-ship communication, and more that are otherwise hidden from view. Discovering these frequencies is an interesting and rewarding challenge in and of itself, and seeing these processes firsthand shows so much about the infrastructure that makes our world run.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="friends">Friends</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Amateur radio is a hobby more or less predicated on talking to people, so it&#x27;s critical to find a group of people you enjoy talking to. Radio communication takes a few forms:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">planned contacts between two or more stations (e.g. me talking to my friends)</li>
<li class="my-2 pl-2"><strong>nets</strong>, often hosted on a repeater (for VHF/UHF) or specific frequency (for HF) at a specific time</li>
<li class="my-2 pl-2"><strong>contesting</strong>, usually on HF, in which stations try to contact as many other stations as possible within a specific timespan (usually a day)</li>
<li class="my-2 pl-2"><strong>DXing</strong> (where DX is slang for &quot;distance&quot;), also usually on HF, which usually involves making contacts with distant radio status and can involve digital protocols such as <a href="https://en.wikipedia.org/wiki/FT8" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">FT8</a></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">HF equipment is more expensive and generally incompatible with apartment life, which means I&#x27;m stuck with a handheld for personal use. That said, I&#x27;ve done some contesting with <a href="https://nuwireless.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Northeastern University Wireless Club</a> and my friend <a href="https://philo.gay/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Philo</a> is an avid FT8 enjoyer -- it&#x27;s possible!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Regardless, this leaves me with nets as one of my only options. While I&#x27;ve tuned into a few on repeaters around Boston, I generally am not interested in the conversation that goes on there. (I can only take so much listening to boomers talk about their medical problems.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Despite how it may seem at first glance, there are a lot of young people in the hobby! Younger radio operators tend to form much more insular groups with their friends or at their university or hackerspace. A good place to spot the younger crowd is often on university-run repeaters -- around Boston, good options include <a href="https://nuwireless.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">W1KBN</a> or <a href="https://web.mit.edu/w1xm/www/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">W1XM</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If not for my close friends in the hobby, I would likely have nowhere near the level of involvement that I do now. We often use radios as an interesting and unique way to coordinate adventures together. My friend Ari created her own <a href="https://adryd.com/pages/travel-bandplan/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">bandplan</a> of simplex frequencies that we often use to communicate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Radio can be a daunting hobby to get into due to its social nature. Finding your people is critical to having a good experience -- I had a handheld collecting dust on my desk for about a year before I really started using it. The first step in the hobby for many is becoming licensed, which often means working up the courage to show up to an unfamiliar place and take an exam administered by an unfamiliar group of people. Part of the reason I recently became a Volunteer Examiner is to help reduce this barrier to entry among friends, and to give me a better answer when someone I know asks how to get involved.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As far as hobbies go, amateur radio has a lot to offer, and not fitting the stereotypical demographic shouldn&#x27;t hold you back from carving your own niche in the world of radio communications.</p></div>]]></description>
            <link>https://breq.dev/2025/01/05/amateur-radio</link>
            <guid isPermaLink="false">/2025/01/05/amateur-radio</guid>
            <category><![CDATA[radio]]></category>
            <pubDate>Sun, 05 Jan 2025 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[My Home Network Lab Notebook]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Some of you might know that <a href="https://avasilver.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a> and I recently moved to our new home in Somerville! As part of this, I finally got the space to start having fun in building a home network setup.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While the setup is pretty tame for now (and Ava has specifically banned me from purchasing any rackmount gear -- alas...), I hope to keep this post updated with notes I have as I work to build more.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="core-equipment">Core Equipment</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="modem">Modem</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We get internet through a cable provider, so the network starts with a modem. I picked a basic Arris SURFboard with DOCSIS 3.2 support since I wanted the flexibility afforded by running a separate router.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This modem operates a little strangely: it gives itself <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">192.168.100.1</code>, hands out <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">192.168.100.10</code> to the first device it sees on DHCP, then also forwards along the assigned public IP address to the connected device. While waiting for the pfSense router to arrive, we actually had to use a Gl.inet router since neither the modem nor our access point has no routing functionality.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="router-and-switching">Router and Switching</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My router is a small Netgate box running pfSense that was gifted to me by my friend <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a>. It has two LAN ports: one goes to the network switches, and the other goes to the Wi-Fi access point. This lets the router and access point use VLANs to separate out clients on each SSID.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I keep the travel router I was using in the interim around for any networking shenanigans that might require it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I have two Netgear switches I picked up at MIT Swapfest a while ago: one sits near the rest of the equipment on my shelf, and the other sits on my desk to connect to my desktop and oscilloscope. One of these switches was featured in my friend Hunter&#x27;s <a href="https://pixilic.com/devices-of-all-time" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Devices of All Time</a> blog post, which I somehow didn&#x27;t realize until months later :)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wi-fi">Wi-Fi</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After dealing with tons of Wi-Fi dropouts in our old apartment, I decided to invest in a Ubiquiti U6 Mesh access point. It&#x27;s definitely overkill, but we get exceptional connection to anywhere in the apartment.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was very excited to finally have a router that didn&#x27;t look like an over-the-top gaming device. The U6 Mesh is very compact and fits nicely on a shelf -- it&#x27;s about the size of a tall seltzer can. The PoE power is actually quite useful in a home environment -- the cable run is a lot tidier with only a single cable to the device. The only criticism I have so far is that gets pretty warm when it&#x27;s running.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While it&#x27;s theoretically possible to set up the device without a UniFi Controller, I couldn&#x27;t figure out how to do so (the app kept crashing when I tried to go through the flow). So, I just installed the UniFi server software onto my desktop and ran it so I could configure the device, then closed out of it. The software allows creating any number of separate SSIDs, which we use to run our primary network, an SSID matching my parents&#x27; home in Maine, and an Eduroam hotspot.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="layer-3">Layer 3</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our IPv4 subnet runs on <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">10.0.0.0/24</code>, mostly since it makes it possible to use <a href="https://en.wikipedia.org/wiki/IPv4#Address_representations" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">shorthands</a> like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">10.2</code> instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">10.0.0.2</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our also ISP provides us IPv6 - yay!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I use pfSense&#x27;s Dynamic DNS tools to point <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">home.breq.dev</code> at our IPv4 address. I couldn&#x27;t get this to work for IPv6 (the router would use its own address, where I wanted to direct traffic to another point on the network). So I put our IPv6 prefix directly into the DNS dialog on Cloudflare and hoped for the best.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="ipv6-addressing">IPv6 Addressing</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Getting the &quot;suffix&quot; of my IPv6 address to remain static ended up being a hassle -- I wanted my computer to continue to respect the prefix it was given, but to keep the same suffix part instead of randomly generating it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I found <a href="https://man7.org/linux/man-pages/man8/ip-token.8.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ip token</code></a> to solve this problem.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To try things out, I ran:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic"># Accept Router Advertisements to configure the network prefix</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> sysctl -w net.ipv6.conf.enp37s0.accept_ra</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># Set an IP token identifier (::bc for my initials)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">ip</span><span class=""> token </span><span class="" style="color:#404040">set</span><span class=""> ::bc dev enp37s0</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, to make it persistent:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic"># add net.ipv6.conf.all.accept_ra=1 in the ipv6 section</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">nano</span><span class=""> /etc/sysctl.conf</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># NetworkManager equivalent of the ip command</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># My wired network is called &quot;Ethernet&quot;,</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># or by default it&#x27;s something like &quot;Wired Connection 1&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> nmcli connection modify Ethernet ipv6.addr-gen-mode eui64</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">sudo</span><span class=""> nmcli connection modify Ethernet ipv6.token ::bc</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="split-dns">Split DNS</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For IPv4, since we do NAT, I needed to use Split DNS to make sure services were available both on and off the network. (And since the DNS resolver built into pfSense was synthesizing records for these addresses, I also needed to have it synthesize <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AAAA</code> records accordingly too.)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="services">Services</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ve got a few services hosted here, with more potentially to come:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://home.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">home.breq.dev</a>, largely just a landing page for now hosted with Caddy from my desktop</li>
<li class="my-2 pl-2">a Nextcloud instance with our home files</li>
<li class="my-2 pl-2">the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">LED matrix</a>, running through a Cloudflare Tunnel for now</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the future, I&#x27;d love to also spin up</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">a &quot;home node&quot; for syncing my files with Syncthing</li>
<li class="my-2 pl-2">a permanent place for the UniFi controller software other than my Ubuntu desktop</li>
<li class="my-2 pl-2">a VPN host, for remote maintenance, getting around firewalls, and maybe hooking up my friends&#x27; networks and mine to make a mega home network</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Eventually, I&#x27;d love to have a dedicated server for lots of these things (Nextcloud, Syncthing, etc) and maybe a reverse proxy to point to other devices on the network? It&#x27;s not a huge priority, but maybe after I upgrade my desktop machine I&#x27;ll be able to scrounge together some hardware.</p></div>]]></description>
            <link>https://breq.dev/2024/10/22/home-network</link>
            <guid isPermaLink="false">/2024/10/22/home-network</guid>
            <category><![CDATA[networking]]></category>
            <category><![CDATA[ipv6]]></category>
            <category><![CDATA[web]]></category>
            <pubDate>Tue, 22 Oct 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Make a FiSSH]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1364" height="1012" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffissh%2Ffish.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffissh%2Ffish.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffissh%2Ffish.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">fissh.breq.dev (&quot;make a fissh&quot;) is a terminal-based fish generator that you access via SSH terminal. At 11:11, try running:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">ssh</span><span class=""> fissh.breq.dev</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">and you&#x27;ll see a fish appear in your terminal!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="fish-generators">Fish generators</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friend <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a> introduced me to the original <a href="http://makea.fish/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">makea.fish</a> site (HTTP only), made by <a href="https://weepingwitch.github.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">weepingwitch</a>. The site embeds an image rendered with PHP showing either a randomly-generated fish or the phrase &quot;come back at 11:11.&quot; Getting the 11:11 fish has become somewhat of a ritual among my friends (we have a Discord channel specifically for posting fish screenshots!)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Over the last few months, we&#x27;ve made a few fun variations on this:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://queercomputerclub.ca/projects/quecey-voip/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">A phone number that responds with an SSTV-encoded fish</a> by <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">adryd</a> and <a href="https://www.blackle-mori.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">blackle</a></li>
<li class="my-2 pl-2"><a href="https://tris.fyi/dish/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;22:22 Bake a Dish,&quot; using random Allrecipes photos</a> by <a href="https://tris.fyi/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">tris</a></li>
<li class="my-2 pl-2"><a href="https://fish.lftq.in/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">A rotating Minecraft-style fish</a> by <a href="https://lukefelixtaylor.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AlpacaFur</a></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">&quot;Make a fissh&quot; was written by myself and my girlfriend <a href="https://avasilver.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ava</a> based on an idea from my girlfriend <a href="https://miakizz.quest/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mia</a>. It&#x27;s our contribution to the weird and wonderful world of fish generators.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wish-bubble-tea-and-lip-gloss">Wish, bubble tea, and lip gloss</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I got the idea for an SSH-based application from <a href="https://terminal.shop/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">terminal.shop</a> (an SSH-based coffee beans store). My first idea was to create a user whose login &quot;shell&quot; is just the fish application, similar to how <a href="https://git-scm.com/docs/git-shell" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">git-shell</a> only allows running Git commands. However, I couldn&#x27;t find a way to make OpenSSH require neither a password nor a public key. I suppose there&#x27;s definitely a good reason for that, but it meant I needed to look elsewhere.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Looking into how terminal.shop implemented their solution, I stumbled upon <a href="https://github.com/charmbracelet/wish" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Wish</a>, a Go library designed for &quot;SSH apps.&quot; I decided to build this within the <a href="https://charm.sh/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Charm</a> ecosystem, using Wish, <a href="https://github.com/charmbracelet/bubbletea" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Bubble Tea</a> for layout, and <a href="https://github.com/charmbracelet/lipgloss" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Lip Gloss</a> for &quot;styling&quot; (i.e., ANSI escape codes).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This stack imposed some requirements on the project structure: this was probably my first model-view-controller app since taking Fundamentals of CS 2 and I definitely would not have picked Go first. The MVC architecture works fine for something like this (although I would&#x27;ve preferred something a bit more component-based / React-y). I found the docs for the Charm projects pretty lackluster, and was often faced with undocumented behavior or limitations that I couldn&#x27;t find reference of online. (I probably should&#x27;ve realized that the number of GitHub stars probably didn&#x27;t correlate to the amount of actual use that the library gets...)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I won&#x27;t pretend to have insightful commentary on Go after using it for a tiny toy project, but I honestly struggle to see myself reaching for it in any situation. My first impression is that it&#x27;s the complexity of Rust without any of the error handling, memory safety, or performance benefits, with more than its fair share of strange syntax or stdlib quirks, coupled with tooling that makes strange decisions at times.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, for a toy project, I got a huge amount of learning out of this, so I can&#x27;t really complain :)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="a-fishy-dataset">A fishy dataset</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With the stack out of the way, it&#x27;s time to actually build this thing. The most important part of a fish generator is the fish it generates. I decided to go the easy route and source fish ASCII art from <a href="https://ascii.co.uk/art/fish" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ascii.co.uk</a>. One thing I like is that most artists leave their initials somewhere in the drawing. I wish I could link to some of the ASCII artists directly from the fish page, but unfortunately, the site doesn&#x27;t give any other attribution.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="finishing-touches">Finishing touches</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1364" height="1012" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffissh%2Fabout.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffissh%2Fabout.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffissh%2Fabout.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Getting the user&#x27;s timezone right is another integral part of fish generation. Web-based generators get this easily from the JavaScript <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Date</code> API, but for other protocols, it can take some creativity. The aforementioned SSTV fish telephone line, for instance, just accepts any time ending in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">:11</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With a terminal, though, we get a bit more information: the user&#x27;s IP address. The app uses <a href="https://ipinfo.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ipinfo.io</code></a> as a geolocation database since their free tier seemed generous and they helpfully provide a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">timezone</code> key in their output. As such, the server can ensure each user gets the fish in their corresponding timezone (assuming the GeoIP database is accurate).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">No app would be complete without a short &quot;about&quot; page, let alone one with so many friends to credit. I took advantage of <a href="https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OSC 8 escape sequences</a> to embed links to the fish dataset and various folks&#x27; websites. These work in many popular terminal emulators, but not all, and there&#x27;s no method to query if a terminal supports OSC 8 and fall back to including full URLs otherwise.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="deployment">Deployment</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Deploying something that runs on port 22 on a server that you presumably also need to SSH into is an annoying situation, and I&#x27;ve had enough bad experiences messing up an SSH config and locking myself out of a VPS that I decided to just spin up a new one for this. (Oracle Cloud Free Tier my beloved!) That said, switching OpenSSH to port 2222 and running this on port 22 largely went off without a hitch.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1364" height="1012" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffissh%2Fcomeback.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffissh%2Fcomeback.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffissh%2Fcomeback.png&amp;w=3840&amp;q=75"/></div></div>]]></description>
            <link>https://breq.dev/projects/fissh</link>
            <guid isPermaLink="false">/projects/fissh</guid>
            <category><![CDATA[go]]></category>
            <category><![CDATA[ssh]]></category>
            <category><![CDATA[networking]]></category>
            <pubDate>Wed, 25 Sep 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Why You Should Write an Emulator]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">One of my favorite quotes is part of <a href="https://medium.com/@bre/the-cult-of-done-manifesto-724ca1c2ff13" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">The Cult of Done Manifesto</a> by Bre Pettis and Kio Stark:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Those without dirty hands are wrong. Doing something makes you right.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So do it. Go write an emulator. Pick a programming language. Pick a basic CPU architecture: either something retro like the <a href="https://www.pagetable.com/c64ref/6502/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">6502</a> or <a href="https://clrhome.org/table/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Z80</a>, or something modern but still simple like the <a href="https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RISC-V</a> base integer instruction set, or something designed for teaching like <a href="https://github.com/StanWarford/pep9" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Pep/9</a>. Implement a basic model of its registers and a simple block memory interface. Start with a few opcodes, write some assembly programs to test them out, and then add more.</p>
<div class="mx-auto my-8 w-full max-w-xl border border-black"></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By the time I sat down and wrote an emulator, I had taken courses on the architecture of a CPU! And yet writing this one piece of software fundamentally changed my understanding of how computers work at a basic level. Computers are obviously far more complicated than the toy examples I&#x27;m suggesting, but advances like branch prediction and caching did not fall out of a coconut tree; they exist in the context of all that came before them.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Concepts like <a href="https://www.youtube.com/watch?v=8Dcj19KGKWM" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Return-Oriented Programming</a> require a strong ability to <em>reason about</em> how machine code is executed. Working with computers at the lowest level through emulator development helped me develop this reasoning ability far better than a class would.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another underrated skill in software development (that I especially think isn&#x27;t taught well!) is reading a datasheet and translating that to a software implementation. Writing software in the real world involves working with lots of weird protocols with documentation of varying quality: on Rover I work with tons of random sensors and modules, at work I write code that communicates with lasers with protocols that vary widely between &quot;very reasonable&quot; to &quot;moderately insane,&quot; and in personal projects I frequently find myself reaching to implement communication with a part that lacks a comprehensive library. While implementing my emulator, I reached for datasheets, schematics, and any documentation I could find. And where the docs weren&#x27;t clear, I was forced to find ways to answer questions about the protocol experimentally.</p>
<div class="mx-auto my-8 w-full max-w-xl border border-black"></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I started, it felt like almost all resources I found on emulator development tried to talk me out of it:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Good knowledge of the chosen language is an absolute necessity for writing a working emulator, as it is quite complex project, and your code should be optimized to run as fast as possible. Computer emulation is definitely <strong>not</strong> one of the projects on which you learn a programming language.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">- <a href="http://fms.komkon.org/EMUL8/HOWTO.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;How To Write a Computer Emulator&quot; by Marat Fayzullin</a></p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Writing an emulator was my first project in the Rust programming language, and I don&#x27;t regret it at all. An emulator forces you to think deeply about your code&#x27;s structure: in the real world, retro computers reused chips for different purposes, split functionality across different parts of the system, and were generally full of leaky abstractions. How do you write a program which encapsulates that structure? Mirroring the structure exactly <a href="https://www.mamedev.org/about.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">a la MAME</a> leads to convoluted code, but building too many abstractions leads to quite complex data flow. And if you&#x27;re anywhere near as much of a perfectionist as me, you&#x27;ll rewrite each component at least three times, agonizing over the small details and using every tool your programming language offers you. And each time you rewrite it, you&#x27;ll learn something.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is my belief that there is no single project which introduces you to the good, the bad, and the ugly aspects of a programming language than an emulator.</p>
<div class="mx-auto my-8 w-full max-w-xl border border-black"></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In classes, computer science assignments are typically <em>rigidly defined</em> (you&#x27;re given an exact set of functionality to implement) and <em>small-scope</em> (you write code once and never touch it again). There&#x27;s great value in just sitting down by yourself a few times a week and building something: not knowing what exactly you&#x27;re making nor what &quot;done&quot; even looks like. Being self-guided is an incredibly useful skill: in the real world, you won&#x27;t have assignment descriptions or TAs to guide you to a solution, and there&#x27;s often no exact description of what a solution looks like. Going from nothing to a functional, complex emulator shows mastery over this skill.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Large-scope projects are also fundamentally different to work on. While I&#x27;m all for building small, toy projects to learn technologies or solve simple problems, they don&#x27;t help you learn how to structure a program. While group work can help, I&#x27;d make the case that it&#x27;s better to learn design patterns in a solo project since you&#x27;ll never encounter a pattern you aren&#x27;t comfortable with. An emulator is usually a large enough project that you can&#x27;t fit the entire thing in your head at once, meaning you&#x27;ll need to rely on good design to help you navigate it.</p>
<div class="mx-auto my-8 w-full max-w-xl border border-black"></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Realistically, the code you&#x27;re writing probably looks nothing like an emulator. Maybe you&#x27;re doing frontend development, or writing a backend CRUD app, or even doing some embedded work. That said, I hope I&#x27;ve made the case for how writing an emulator is an immensely useful exercise regardless of the flavor of software development you do.</p></div>]]></description>
            <link>https://breq.dev/2024/09/14/emulator</link>
            <guid isPermaLink="false">/2024/09/14/emulator</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[projects]]></category>
            <pubDate>Sat, 14 Sep 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[88x31 Dungeon]]></title>
            <description><![CDATA[<div class="e-content font-body"><br/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">88x31 Dungeon is a set of &quot;games&quot; that allow you to traverse the graph of <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/eightyeightthirtyone">88x31 buttons</a>, which are small, pixel art buttons that website owners include to link to related people and projects. It&#x27;s based on data collected by <a href="https://eightyeightthirty.one/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">eightyeightthirty.one</a>, a scraper which originally mapped the full 88x31 network. The graph is used to generate a &quot;dungeon&quot; grid, with each room containing a particular website. This project can be thought of as an alternate way to visualize and interact with the graph created by eightyeightthirty.one.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="experiences">Experiences</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">88x31 Dungeon provides a set of related experiences, each providing a different way to interact with the graph.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="birds-eye">Birds Eye</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3360" height="1930" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdungeon%2Fbirdseye.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fdungeon%2Fbirdseye.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The birds-eye view shows the full grid and allows the user to scroll across the map. This was the first and simplest experience I implemented, and is useful mostly to visualize the layout of the dungeon. Each square renders the site&#x27;s 88x31 and domain name, and it&#x27;s possible to &quot;fly&quot; to a specific site.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="navigate">Navigate</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3360" height="1930" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdungeon%2Fnavigate.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fdungeon%2Fnavigate.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Navigate embeds each site with an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;iframe&gt;</code> and allows you to visit its neighbors using arrow keys. It&#x27;s loosely inspired by the &quot;dungeon crawler&quot; format, but the arrow buttons at the top let you see the domain of the site you&#x27;re about to navigate to. It&#x27;s almost like a two-dimensional <a href="https://en.wikipedia.org/wiki/Webring" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">webring</a>. I find it fun to move quickly through this one, getting a feel for each region of the dungeon by seeing each site for a few seconds.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="walk">Walk</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2338" height="1674" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdungeon%2Fwalk.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fdungeon%2Fwalk.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Walk presents a draggable canvas on which rooms of the dungeon are embedded as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;iframe&gt;</code>s. The buttons at the left allow you to switch between &quot;drag mode&quot; and &quot;interact mode,&quot; where the latter allows you to scroll, click, and interact with each website on the grid. Unlike the other frame-based experiences, Walk shows up to nine frames at once, letting you see each site in the context of its neighbors.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A limitation of Walk is that each site is rendered in a relatively small viewport, breaking some sites. This is particularly an issue on mobile browsers where the viewport is already very small.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="crawl">Crawl</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1610" height="1866" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdungeon%2Fcrawl.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdungeon%2Fcrawl.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdungeon%2Fcrawl.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Crawl is the most similar experience to a text-based dungeon crawler. At the top, the site is embedded in a 4:3 <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;iframe&gt;</code>, and at the bottom, the user can run commands in a terminal.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Crawl provides the following commands:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">go [dir]</code>: Move in a direction (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">north</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">southeast</code>, etc)</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">look</code>: Look around (prints the domains of the rooms that border the current room)</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fly [domain]</code>: Teleport to a specific room</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project consists of two parts: the code to generate the dungeon, and the frontend experiences.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dungeon-layout-generation">Dungeon Layout Generation</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1692" height="1416" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdungeon%2Fdungeon-full.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdungeon%2Fdungeon-full.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdungeon%2Fdungeon-full.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A full view of the generated dungeon layout.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The dungeon is generated using a breadth-first greedy algorithm, which is roughly defined as:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">Start by placing <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">breq.dev</code> at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">(0, 0)</code> and adding its neighbor cells to the queue</li>
<li class="my-2 pl-2">Pull a grid cell from the queue</li>
<li class="my-2 pl-2">Find domains with links (in either direction) to the rooms neighboring this cell</li>
<li class="my-2 pl-2">Sort the candidate domains by the number of unique links</li>
<li class="my-2 pl-2">If no candidates exist, return to step 2</li>
<li class="my-2 pl-2">Place the best candidate in a room at this cell</li>
<li class="my-2 pl-2">Add the neighbors of the newly placed cell to the queue</li>
<li class="my-2 pl-2">Return to step 2</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m happy with how this algorithm creates neighborhoods, and it places a good portion of the total graph. However, it struggles with cliques, since each node can have at most 4 neighbors.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="putting-sites-in-frames">Putting Sites in Frames</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each experience is implemented differently, but most required embedding untrusted sites using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;iframe&gt;</code> tags. Iframes are a relic of the old web, and honestly probably would not become a web standard if they were proposed within the last few years. A few issues I ran into included:</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="react-reusing-frame-elements">React reusing frame elements</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Previously, some parts of the implementation looked like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">iframe</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">src</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#1bb3ff">`</span><span class="" style="color:#1bb3ff">https://</span><span class="" style="color:#ff218c">${</span><span class="" style="color:#ff218c">room</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">domain</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#1bb3ff">`</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The subtle bug here that creates undesired behavior is that when <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">room</code> changes, the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">iframe</code> element is reused instead of recreated, so the old frame contents persist while the new ones are loaded. This leads to a confusing user experience, especially in Walk where frame positions are constantly changing positions and sources.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">React provides a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">key</code> parameter for lists to tell what elements to create, destroy, or reuse. The best solution I found in this case is to create a 1-element list and use this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">&gt;</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">iframe</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">src</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#1bb3ff">`</span><span class="" style="color:#1bb3ff">https://</span><span class="" style="color:#ff218c">${</span><span class="" style="color:#ff218c">room</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">domain</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#1bb3ff">`</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">key</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">room</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">domain</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">&gt;</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dead-links-leading-to-frames-not-loading">Dead links leading to frames not loading</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you scroll for any appreciable amount of time, you&#x27;re likely to find either the &quot;page crashed&quot; icon (in Chrome) or a &quot;Firefox can&#x27;t display this page&quot; error (in Firefox). Unfortunately, many targets of 88x31 links are dead pages. While I&#x27;d love to show a fallback UI containing the 88x31 button itself in these cases, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#error_and_load_event_behavior" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">IFrames don&#x27;t send <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">error</code> events</a> since it could be used to &quot;probe the URL space of the local network&#x27;s HTTP servers.&quot; Since there are many other ways to probe the URL space of the local network&#x27;s HTTP servers from a website, I&#x27;m not sure what the merit of this policy is, but as web developers we must accept that we live and die at the whim of browser makers.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Amazingly, <a href="https://stackoverflow.com/questions/375710/detect-failure-to-load-contents-of-an-iframe/54952975#54952975" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">someone found a batshit insane way to solve this problem</a>, but I&#x27;m hesitant to use a brittle solution like this.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sites-setting-the-x-frame-options-header">Sites setting the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">X-Frame-Options</code> header</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A lot of sites set <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">X-Frame-Options</code> header</a> (or its modern alternative, the Content Security Policy directive <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">frame-ancestors</code></a>) to prevent untrusted websites from loading them as frames.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There is a legitimate security argument for some sites due to an attack called <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Clickjacking" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Clickjacking</a>, in which trusted content is loaded into an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;iframe&gt;</code> and buttons, input fields, or other interactive elements are overlaid on top of the frame to capture input. However, this isn&#x27;t applicable to most simple personal websites.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While it might be possible to build and use a proxy which strips these headers from the site, doing so would violate the spirit of this header: if someone set this header, they would probably prefer their site not be embedded into other sites, and I don&#x27;t want to override their preference. (I&#x27;d still really prefer to show a fallback... alas.)</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sandboxing-frames">Sandboxing frames</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">During early development, I found a few sites acting annoyingly, including one that would navigate the top-level window away (what the hell?). While I&#x27;d love it if everyone on the indie web played nice, I did implement some protections using <a href="https://web.dev/articles/sandboxed-iframes" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">sandbox</code> attribute</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I added the following flags:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">allow-scripts</code>, since JS in frames is mostly harmless</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">allow-same-origin</code>, since leaving this out interferes with many websites&#x27; ability to load fonts and other resources</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This still lets frames cause problems (lagging the user&#x27;s computer, autoplaying annoying audio, etc), but the worst of the problems have been dealt with. Plus, this project deciding what&#x27;s annoying and restricting features of iframes runs counter to the spirit of showcasing the variety present in the indie web. As such, this approach strikes a balance between maintaining the availability of the site and allowing for creative expression.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While some parts are a little clunky, the intent behind this was less a polished end result and more an experiment into designing interesting and fun experiences which are part game and part visualization tool. I&#x27;m happy with the variety present in the end result. Undoubtedly I think I&#x27;m going to end up finding more ideas for experiences, and I&#x27;m excited to explore this idea further.</p></div>]]></description>
            <link>https://breq.dev/projects/dungeon</link>
            <guid isPermaLink="false">/projects/dungeon</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[88x31]]></category>
            <category><![CDATA[web]]></category>
            <pubDate>Sat, 07 Sep 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Thoughts on Cities, Navigation, and Home]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">To paraphrase a close friend of mine:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Boston is like a Grand Theft Auto map. There&#x27;s a lot to do, but it never really feels all that spread out.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I moved to Boston almost three years ago from today. What I love most about this city is how accessible it is. Here are, roughly chronologically, the ways I&#x27;ve picked up to navigate here, and my reflections on city life.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-subway">The Subway</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By far the most obvious answer to &quot;how do you get around?&quot; is the subway. Despite distinctly remembering a CharlieCard shortage for around a month after I arrived in the city, I quickly started using the system to explore.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The subway gave me direct access to downtown, and somewhat efficient access to many other places around the city. But using it also feels kind of like &quot;fast travel&quot; in a video game: you hit a button somewhere, wait for the map to load in, and boom, you&#x27;re somewhere else, bypassing all of the beautiful level design in between.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="walking">Walking</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I first moved here (and didn&#x27;t have many friends), I would sometimes pass the time by just stepping outside, pointing myself in a random direction, and walking. When I would get tired, I would start looking for a T stop or just turn around and head home.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Shortly after, I started challenging myself to walk places without looking at any map: learning street names, orienting myself based on the skyscrapers and cranes overhead, and taking plenty of wrong turns in the process.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I love the chaotic street network of Boston, and how it always gives the sense that there&#x27;s more to explore.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Walking puts me on the same scale as the world around me. I can look into any storefront, stop to peek down side streets, or take in the scenery around me. To this day I&#x27;ll still happily walk impractical distances from time to time for no reason other than to take in my surroundings.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-bus">The Bus</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It took me about a year before I started taking MBTA buses. Many friends I&#x27;ve talked to share the same experience. I&#x27;m not sure if it&#x27;s because the bus network is harder to understand, buses are the more &quot;looked down upon&quot; form of transit, or if those who are new to city life, like my past self, are just less aware of the utility of local bus routes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve found the social dynamics on buses to be quite different to the subway. You&#x27;ll see tons of people piling into the back doors on the Green Line to avoid paying the fare, but you almost never see that on buses. On the bus, there&#x27;s a much stronger sense of &quot;we&#x27;re all in this together.&quot;</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Buses also let me expand my radius of places I can explore. A few of my friends live in places vaguely accessible by infrequent bus routes, and while we&#x27;ll usually opt for the more practical option of meeting at a subway line terminus, it&#x27;s fun to occasionally take a long, winding transit journey.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="commuter-rail">Commuter Rail</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While the most expensive transit mode on this list, the commuter rail is hugely useful both for going beyond the confines of the city and quickly getting to and from downtown (provided you time it right). Honestly, it&#x27;s one of the modes I wish I took advantage of more.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Also: the $10 weekend passes are an insanely good deal. Use them.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="bluebikes">BlueBikes</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I didn&#x27;t start biking in the city until recently, but I immediately fell in love with it. Biking immediately felt like the &quot;just right&quot; scale: when riding, you can see every detail of every street, you have full autonomy to stop and explore and detour to your heart&#x27;s content, but you also get places <em>fast</em>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I was young, I used to watch those Casey Neistat vlogs where he lane-splits through NYC traffic on a skateboard, and I always wished I could experience a feeling like that. Biking gives me that excitement: navigating complex situations at high speed, finding miniscule ways to shave time off of trips, and playing my small part in the chaotic yet coordinated motion of traffic.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I expected biking in Boston to be scary, mostly because people tend to make it out to be. But coming from narrow, rural, two-lane roads with large trucks whizzing by at highway speeds, the city feels like a cakewalk by comparison. Sure, you&#x27;re navigating way more interactions with cars than you would be otherwise, but you don&#x27;t get nearly the speed differentials of somewhere more rural.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, if you take anything away from this post, wear a helmet. One of my most &quot;I can&#x27;t believe I didn&#x27;t buy this sooner&quot; purchases was a nice folding helmet that fits in my backpack, so I&#x27;m ready to ride safely whenever.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cars">Cars</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My girlfriend and I are looking to buy a car soon. While I&#x27;m excited to have easier access to places beyond the city, I&#x27;m simultaneously nervous that the convenience of car trips will make me lose out on the connection to the city that life without a car has allowed me to build over the past three years.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To end on an anecdote: Once, after a robotics competition in rural Utah, a close friend of mine picked me up to drive me to her home in New Mexico. Sitting on the center console was a notebook on which she had written the numbers: 70, 191, 491, 160, 84 -- the highway numbers we followed on our drive back.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I asked about the notebook, and she said it she was trying to use Google Maps less since she felt it made her less connected to the areas she drove through: instead of looking out the window to understand and be present in the places you drive through, it encourages you to just follow the arrows, ignoring the world around you.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Whenever I navigate a place by memory or intuition, be that the chaotic streets of Boston, the sprawling MBTA bus network, the back roads of small towns in Maine, or the Albuquerque airport I hadn&#x27;t been through in a year, I feel, in a way, welcomed by the communities I pass through. I feel nostalgic for the times I passed through those same places before. I feel excited to explore, knowing that wrong turns can be less of an annoyance, more of an adventure. I feel grounded, able to anchor my experiences to the world happening around me as I traverse through it. Above all, I feel content, no longer stressed by the arrow on my phone screen, just present in the here and now.</p></div>]]></description>
            <link>https://breq.dev/2024/08/15/boston</link>
            <guid isPermaLink="false">/2024/08/15/boston</guid>
            <category><![CDATA[boston]]></category>
            <category><![CDATA[life]]></category>
            <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Rolodex]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2226" height="1434" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Frolodex%2Fmine.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Frolodex%2Fmine.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">My own card as it appears in my account.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Rolodex stores a virtual contact card with a callsign, name, and other metadata for each of your amateur radio friends. Cards can be viewed, created, and updated on a desktop or mobile device.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friends and I often use local amateur radio repeaters to communicate, so I often need to call one of them by callsign. I didn&#x27;t feel like I had a centralized place to store this information: a text note would be difficult to navigate and annoying to sync between devices, a traditional contacts app lacks an appropriate field for callsign or DMR information, and I wanted something that I could search by callsign in case I can&#x27;t quite remember who holds a particular call. The resulting app is a single place where I can track all of my friends&#x27; calls, and is something I can easily reference from my phone in the field.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I chose the name &quot;Rolodex&quot; because it conveyed the purpose of the app (storing contacts), and it sounded unique, functional, and a little playful.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The frontend uses the stack I tend to reach for for frontend work: Vite, TypeScript, React, and Tailwind.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cards use a fixed aspect ratio to follow the <a href="https://en.wikipedia.org/wiki/ISO/IEC_7810#ID-1" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ID-1</a> spec. I used the morse code version of the callsign as an artsy divider between the callsign and name -- this uses a specific morse code font file. The interface uses a mix of DIN and JetBrains Mono, two fonts I haven&#x27;t worked with much before, since I felt they worked together well and gave each card a utilitarian look without being off-putting.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The &quot;rolling&quot; animation in the column view works by checking where each card is in the scroll view with a JavaScript event, then applying rotation in X and translation in Y and Z. This took me the most effort to get right, and I&#x27;m probably going to continue tweaking it to improve its behavior across various screen sizes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The backend of the app is entirely done in Firebase. This was my first Firebase project, and I figured it was a good fit since the requirements are quite standard (it needs a simple authentication system and a basic way for each user to store and retrieve a small amount of data).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The app is a Progressive Web App, allowing me to install it on the home screen of my phone.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can try it at <a href="https://rolodex.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">rolodex.breq.dev</a>! At the time of writing, we have 5 total users, including myself! Personally I have more than a dozen contacts saved already, and am constantly adding more. I&#x27;ve already found it quite useful, and am looking forward to using it more as more of my friends get licensed and have callsigns issued to them.</p></div>]]></description>
            <link>https://breq.dev/projects/rolodex</link>
            <guid isPermaLink="false">/projects/rolodex</guid>
            <category><![CDATA[radio]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[web]]></category>
            <category><![CDATA[firebase]]></category>
            <pubDate>Wed, 03 Jul 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[My Dream Handheld Computer]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="nostalgia">Nostalgia</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I was younger, I had a <a href="https://www.theverge.com/circuitbreaker/2016/7/19/12227806/pocketchip-review-portable-linux-computer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PocketCHIP</a> handheld that I would carry around. It&#x27;s what I wrote the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/makergamer">MakerGamer</a> fantasy console for, and it&#x27;s the device I first started experimenting with <a href="https://www.open-mesh.org/projects/batman-adv/wiki/Doc-overview" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">batman-adv</a> mesh networking on.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="530" height="942" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I loved being able to have a Linux box with me that I could use for experimentation. The keyboard sucked, and the interface wasn&#x27;t amazing on a tiny-resolution touchscreen, but that didn&#x27;t matter for quick usage, and it was still miles ahead of pulling up Termux on my phone.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Part of what got me thinking about this is seeing a few of my friends pick up <a href="https://flipperzero.one/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Flipper Zero</a> devices. These have that quality of being a device for &quot;real-world&quot; tinkering and exploration, encouraging people to interact with systems in the world around them. The main reason I don&#x27;t have one is just that I&#x27;m not particularly interested in RFID/NFC applications (but I applaud those who are).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">About a year ago I picked up a cheap <a href="https://www.baofengradio.com/products/uv-5r" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Baofeng</a> handheld radio for amateur use. While it&#x27;s a practical way to communicate with friends on a short range, I&#x27;ve found the most fun part is using it to <em>explore the world around me</em> -- finding repeaters and trying to communicate with them, listening on railroad or marine channels, and generally exploring to see what systems out there that I can observe. (See <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/codeplug">codeplug</a> for how I&#x27;ve iterated on this process over time).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I want a device which combines the convenience of a Baofeng, the customizability of a Flipper, and the power and peripherals of a PocketCHIP. But more broadly, I want something that encourages that sort of exploration with a physical element to it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There was a time recently when I was sitting out in a park downtown, poking at some interesting stuff accessible over Wi-Fi that I&#x27;m not quite ready to talk publicly about yet. While I was doing this on my laptop, doing so felt odd given all I needed was a basic environment where I could configure my Wi-Fi settings and invoke SSH. A dedicated handheld device would have been just as functional and a bit more fun.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="my-dream-handheld-computer">My Dream Handheld Computer</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a rough list of requirements that I&#x27;d look for in such a device. This is based on my own interests and the areas of tech I tend to explore most.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Main Processor:</strong> I want something that runs a proper Desktop Linux distro (not Busybox etc), and is powerful enough to run at least a basic Web browser. This probably means a Raspberry Pi compute module or similar.</li>
<li class="my-2 pl-2"><strong>Battery:</strong> At least enough for a full day of running around and using it intermittently.</li>
<li class="my-2 pl-2"><strong>Keyboard:</strong> Full QWERTY. Good enough to use the CLI with.</li>
<li class="my-2 pl-2"><strong>Screen:</strong> This doesn&#x27;t need to be good enough to display something like GNOME well, but should at least allow for basic GUI apps and comfortable terminal use.</li>
<li class="my-2 pl-2"><strong>Hardware Connections:</strong>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">A full RJ45 Gigabit Ethernet port. I&#x27;ve had so many cases where I just want to get on a network quickly and run an SSH command, and breaking out my laptop plus dongle and messing with the macOS network settings is always painfully clunky.</li>
<li class="my-2 pl-2">USB-C (as an Ethernet gadget, etc). For longer development sessions, I&#x27;ll want to connect a proper laptop.</li>
</ul></div>
</li>
<li class="my-2 pl-2"><strong>Radios:</strong>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Wi-Fi is a necessity in today&#x27;s world, Bluetooth would be a nice-to-have.</li>
<li class="my-2 pl-2">NFC capabilities would be cool for people who are into that sort of thing.</li>
<li class="my-2 pl-2">I&#x27;d love some sort of amateur or otherwise &quot;unconventional&quot; radio capabilities, to allow these devices to be used for communication. While drop-in modules for the 2m band are more rare, lots of them exist for the 70cm (430 MHz, licensed) and 33cm (900 MHz, unlicensed) bands. 430 MHz would likely give better range/performance, while 900 MHz gives interoperability with the <a href="https://meshtastic.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Meshtastic</a> network.</li>
<li class="my-2 pl-2">I&#x27;d also like a proper external antenna for the above radio, like you&#x27;d find on an amateur HT.</li>
</ul></div>
</li>
<li class="my-2 pl-2"><strong>Extensibility:</strong> Adding external exposed GPIOs would be cool, but also, I haven&#x27;t seen many people taking advantage of them with the Flipper Zero or PocketCHIP. Connectors like <a href="https://learn.adafruit.com/introducing-adafruit-stemma-qt" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit STEMMA</a> might be more useful in practice.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="existing-products">Existing Products</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dead-stock-pocketchip">Dead Stock PocketCHIP</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Up until recently, the website <a href="https://shop.pocketchip.co/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">pocketchip.co</a> has sold old PocketCHIPs (and still sells CHIPs, albeit at 4x the original selling price). Even if they were still available, though, the lack of up-to-date software for them makes common tasks like flashing a hassle.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="clockwork-uconsole">Clockwork uConsole</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <a href="https://www.clockworkpi.com/uconsole" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Clockwork uConsole</a> is probably the closest thing I&#x27;ve found to the device I want, but since it&#x27;s designed more for <a href="https://en.wikipedia.org/wiki/Fantasy_video_game_console" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;fantasy console&quot;</a> use, it falls short in a few ways:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Available ports are limited. While it has a USB port for host and device use, it lacks a physical Ethernet port :(</li>
<li class="my-2 pl-2">It features an extension module interface, but currently the only such module is an LTE modem (probably not that useful to me since I could just use my smartphone hotspot). Maybe a module for something like LoRa could be designed using the same connector, with an external antenna poking the side?</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s also still limited to pre-order.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="whatever-arturo182-is-cooking">Whatever arturo182 Is Cooking</h3>
<div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Solder Party has previously sold a <a href="https://www.tindie.com/products/arturo182/keyboard-featherwing-qwerty-keyboard-26-lcd/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;Keyboard FeatherWing&quot;</a> containing an LCD, QWERTY keyboard of the type you&#x27;d find on a Blackberry phone, and a place for an <a href="https://learn.adafruit.com/adafruit-feather/overview" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit Feather</a> board. While a Feather doesn&#x27;t provide quite the amount of computing power I&#x27;d like for such a device, I love the form factor and the pricing is reasonable.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rooted-android-devices">Rooted Android Devices</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What about carrying around rooted Android phone for something like this, or rooting my daily driver? This would give an unrestricted Linux environment to play around with, but it also falls short of what I&#x27;m after:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Poor connectivity: no RJ45, spotty USB host support.</li>
<li class="my-2 pl-2">Not quite &quot;Desktop Linux-y&quot; enough to run apps designed for typical Linux distros.</li>
<li class="my-2 pl-2">Using a CLI with a touchscreen keyboard is painful.</li>
<li class="my-2 pl-2">No real extensibility for something like LoRa.</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="designing-my-own">Designing My Own?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I&#x27;m relatively comfortable with basic PCB design, this is something that&#x27;s likely way beyond my skill level. Plus, I&#x27;d ideally like to have something that can have a community form around it (like PocketCHIP did, or like Flipper Zero does now). There&#x27;s also the enclosure to consider -- PocketCHIP did this relatively well and the Flipper does it perfectly.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m largely ruling this out unless I suddenly meet lots of people who share this vision but have much stronger electrical/mechanical skills than I do.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="something-else">Something Else?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you can think of something that fits in this general category/vibe of product, please let me know about it! Even if it isn&#x27;t what I&#x27;m personally looking for, I really want to learn more about projects in this space.</p></div>]]></description>
            <link>https://breq.dev/2024/06/21/handheld</link>
            <guid isPermaLink="false">/2024/06/21/handheld</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[radio]]></category>
            <pubDate>Fri, 21 Jun 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Codeplug]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1644" height="1312" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fcodeplug.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fcodeplug.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fcodeplug.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The codeplug generation tool in action.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Codeplug is a tool to automatically generate a configuration file (or colloquially, a &quot;codeplug&quot;) for a handheld amateur radio.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The tool prompts users to select their geographic regions of interest and to choose which common simplex channels on amateur and other bands to include (such as calling frequencies, FRS channels, etc.), and builds a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.csv</code> file containing those channels and channels for radio repeaters in their selected regions.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Many of my friends are licensed amateur radio operators, and when we travel together, we often exchange notes on which repeaters to program into our radios. However, as I&#x27;ve made more extensive use of my radio, my previous strategy of including every repeater I can think of in my codeplug has caused me to hit the channel limit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ari</a> and I had the idea for a program which can automatically generate a suitable codeplug based on a subset of regions, channels, etc., allowing us to easily program in relevant channels to our radio based on our current interests, upcoming trips, and more.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The tool is a CLI app built with <a href="https://python-inquirer.readthedocs.io/en/latest/index.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Inquirer</a> for Python. It loads repeater and channel definitions from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.yaml</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.csv</code> files, and assembles a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.csv</code> which can be imported into <a href="https://chirpmyradio.com/projects/chirp/wiki/Home" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">CHIRP</a>, a common tool for editing and uploading these configs.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Instead of continuing to maintain my codeplug <a href="https://github.com/breqdev/chirp" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">by hand</a>, I&#x27;ll be using and improving this tool going forward. A few of my friends have already offered to contribute repeater information to it, and I&#x27;m planning on extending it to include <a href="https://www.on-track-on-line.com/scanner-radio.shtml" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AAR channels</a> by region as well.</p></div>]]></description>
            <link>https://breq.dev/projects/codeplug</link>
            <guid isPermaLink="false">/projects/codeplug</guid>
            <category><![CDATA[radio]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[cli]]></category>
            <pubDate>Thu, 13 Jun 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[DDS Tuning for ROS 2]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Picture this: you&#x27;re developing a robot that you aim to control using a nice, clean user interface. Control goes through this UI on one laptop over a network connection to the robot, which runs its own SBC, all using standard ROS 2. You do a ton of testing, at first just with an Ethernet cable since you don&#x27;t feel like dragging your radios out of the closet, and everything looks good! And then, you show up to an event with your robot, and you realize that whenever you switch views in the UI, suddenly your entire comms link grinds to a halt.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="771" height="254" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Ftraffic-spike.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdds-tuning%2Ftraffic-spike.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Ftraffic-spike.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, what gives?</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="discovery-traffic-and-fragmentation">Discovery Traffic and Fragmentation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This issue is a classic case of discovery traffic trashing your link. Whenever the UI subscribes to a new topic in ROS, the Data Distribution Service (DDS) middleware needs to figure out which node in the network is publishing that topic, so it generates a ton of multicast UDP traffic to send out to the network. This works fine over a high-bandwidth Ethernet link, but causes problems with a bandwidth-constrained radio connection.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When our team hit this, we took some Wireshark captures before and after. Here&#x27;s the normal, working state of the network:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1123" height="479" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-good.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-good.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-good.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And here&#x27;s after we generate a bit of DDS discovery traffic:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1123" height="955" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-bad.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-bad.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-bad.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The unassembled fragments! The horror...</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re not familiar with fragmentation, it&#x27;s something that happens at the network layer to break large packets into smaller ones to fit your particular network&#x27;s maximum transmission unit (MTU), which is usually 1500. This disassembly and reassembly is typically transparent to the user, and is handled at the OS level. The fact that Wireshark is showing these unassembled fragments implies that they couldn&#x27;t be reassembled correctly -- some of the fragments were dropped, and now the message can&#x27;t be recovered.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In other words, this is <em>not</em> cute, computer networks only do this when they&#x27;re in <em>extreme</em> distress.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="tunable-parameters">Tunable Parameters</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The ROS 2 documentation <a href="https://docs.ros.org/en/rolling/How-To-Guides/DDS-tuning.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">suggests</a> a few knobs which you can turn to hopefully improve performance:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Reduce <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">net.ipv4.ipfrag_time</code>.</strong> By default, Linux allows 30 seconds for a packet to be reassembled before giving up and dropping the fragments from memory. If a lot of fragmentation is happening, allowing this much time can fill buffers quickly, and on a small network (on the scale of one robot and its base station), it shouldn&#x27;t ever take more than a few seconds for a fragment to get from one end to the other. ROS recommends a value of 3 seconds.</li>
<li class="my-2 pl-2"><strong>Increase <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">net.ipv4.ipfrag_high_thresh</code>.</strong> This controls the amount of memory used for packet defragmentation. By default it&#x27;s 256 KiB, but in a scenario with lots of fragmentation happening, you can bump this up as high as 128 MB.</li>
<li class="my-2 pl-2"><strong>Increase <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">net.core.rmem_max</code>.</strong> This controls the size of the buffer that the Linux kernel uses for receiving data on a socket. ROS suggests anywhere from 4 MiB to 2 GiB depending on the DDS vendor.</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="alternative-dds-middleware">Alternative DDS Middleware</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One thing to try is changing the actual DDS implementation you&#x27;re using. DDS is an open standard, and there are plenty of implementations to choose from. The popular implementations all have corresponding ROS middlewares written, and it&#x27;s trivial to switch from one to the other.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The default middlewares so far have been:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Ardent, Bouncy, Crystal, Dashing, Eloquent, Foxy: <strong>FastDDS</strong> by eProsima</li>
<li class="my-2 pl-2">Galactic: <strong>CycloneDDS</strong> by Eclipse</li>
<li class="my-2 pl-2">Humble, Jazzy: <strong>FastDDS</strong> by eProsima</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In addition to FastDDS and CycloneDDS, there are ConnextDDS and GurumDDS, but those lack open-source licenses.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Interestingly, Cyclone is seemingly more loved by the community than FastDDS: The MoveIt inverse kinematics tool <a href="https://moveit.ros.org/install-moveit2/binary/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">recommends Cyclone</a>. For what it&#x27;s worth, switching to Cyclone largely fixed our discovery traffic problems:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="761" height="226" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Fcyclonedds.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdds-tuning%2Fcyclonedds.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Fcyclonedds.png&amp;w=1920&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dds-overhead">DDS Overhead</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This <a href="https://cdck-file-uploads-global.s3.dualstack.us-west-2.amazonaws.com/business7/uploads/ros/original/2X/7/76acdd6b89e8faf4b3ab63cd170d7b4fc6bd0924.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">slide deck</a> by Charles Cross goes into detail about some techniques for optimizing ROS 2 traffic. One often-overlooked feature of networking in ROS 2 is the amount of overhead for each message.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a packet containing a command that we sent to the left wheel of our robot. It&#x27;s a single 32-bit floating point number, so the value itself takes up 4 bytes. How much overhead does transmitting this incur?</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3242" height="1878" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-packet.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Fwireshark-packet.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That&#x27;s <strong>138</strong> bytes over the wire:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">14 bytes of Ethernet header</li>
<li class="my-2 pl-2">20 bytes of IPv4 header</li>
<li class="my-2 pl-2">8 bytes of UDP header</li>
<li class="my-2 pl-2">96 bytes of RTSP data
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">20 bytes of RTSP header</li>
<li class="my-2 pl-2">12 bytes of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">INFO_TS</code> submessage</li>
<li class="my-2 pl-2">32 bytes of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">DATA</code> submessage
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">24 bits of header</li>
<li class="my-2 pl-2">4 bytes of encapsulation kind/options</li>
<li class="my-2 pl-2"><em>4 bytes of data (this is our actual message)</em></li>
</ul></div>
</li>
<li class="my-2 pl-2">32 bytes of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">HEARTBEAT</code> submessage</li>
</ul></div>
</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In other words, sending each message requires <em>132 bytes of overhead</em>, regardless of the actual size of the message. With this in mind, we can greatly reduce the load over the network by combining as much as possible into as few topics as possible. In practice, here&#x27;s how we applied this:</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="custom-messages">Custom Messages</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Wherever possible, make use of ROS 2 custom messages. Each new system we build starts with a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">*_msgs</code> package, which includes message definitions tailored to that stack.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are lots of reasons to do this other than performance: it makes the development experience much better by giving meaningful names to values, it catches semantic &quot;mismatches&quot; caused by improperly hooking up a publisher and subscriber, and it helps <em>make invalid states unrepresentable</em> (can you tell I&#x27;m a Rust fan?).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the above example, our team now uses a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">DriveCommand</code> which encodes the speed of each of our six motors in a single message, each named semantically:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><span class="font-bold">DriveCommand.msg</span><div class="my-2 -ml-4 -mr-8 border-b-2 border-black"></div><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">float32 left_front</span></div><div class="" style="color:#404040"><span class="">float32 left_middle</span></div><div class="" style="color:#404040"><span class="">float32 left_back</span></div><div class="" style="color:#404040"><span class="">float32 right_front</span></div><div class="" style="color:#404040"><span class="">float32 right_middle</span></div><div class="" style="color:#404040"><span class="">float32 right_back</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="large-messages-and-fragmentation">Large Messages and Fragmentation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our team ran into another issue with ROS 2: heavy fragmentation with large message types.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While filming a <a href="https://www.youtube.com/watch?v=ocijXlLQ2Es" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">demo video</a> of our system, we wanted to include a visualization of point-cloud data from our stereoscopic camera (a <a href="https://www.stereolabs.com/products/zed-2" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Zed 2i</a> by StereoLabs). Last year, we got the shot (video <a href="https://youtu.be/-ZSak_HEInE?t=238" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>):</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2560" height="1440" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdds-tuning%2Fpoint-cloud.jpeg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fdds-tuning%2Fpoint-cloud.jpeg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We had no trouble doing this in ROS 1 -- as our communication link degraded, the point cloud data just started to slow down. However, in ROS 2, this demo completely broke down.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The key difference here is that ROS 1 used TCP, while ROS 2 uses UDP. Each point cloud message is big: our sensor is quite high-resolution and each pixel needs a color and position in space. With a TCP connection, congestion control takes care of things, slowing down the link and causing frames to be dropped to keep things working -- this slows down the framerate at the output but otherwise works fine.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In ROS 2 using a DDS middleware, messages are sent over UDP. However, remember that frames on most networks are limited to an MTU of 1500. When you send a large message using a DDS, the DDS layer just creates a single massive UDP packet, and the OS is responsible for fragmenting it appropriately. If the computer on the other end can defragment it properly, you&#x27;re in business. In a congested network, the chances of successful reassembly fall to near-zero, meaning you&#x27;ll get nothing out of the other end of the connection.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here, you&#x27;ve got a few options:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Try to control the fragmentation process yourself: avoid sending large messages, instead send smaller ones.</li>
<li class="my-2 pl-2">If you&#x27;re able to, try to reduce the traffic over the network link to pull it out of a congested state (and hope for the best).</li>
<li class="my-2 pl-2">Transport this data through a different transport mechanism. (We do this for <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2023/06/21/cameras">image streaming</a>.)</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="qos-parameters">QoS parameters</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">ROS 2 introduced <a href="https://docs.ros.org/en/humble/Concepts/Intermediate/About-Quality-of-Service-Settings.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Quality-of-Service</a> parameters on subscribers and publishers. A QoS &quot;profile&quot; includes a few parameters (listed here roughly from most to least important):</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>History (Queue Depth):</strong> The number of samples to keep in the queue. Lower numbers improve efficiency, but may lead to old messages being lost if the network drops for a second or so. A sensor readout can usually have a queue depth of 1 since the system only cares about the last read value, but a button press value should have a larger queue depth or it&#x27;s possible to miss inputs.</li>
<li class="my-2 pl-2"><strong>Reliability:</strong> &quot;Best Effort&quot; or &quot;Reliable.&quot; Controls whether publishers should retry transmission to subscribers if it fails.</li>
<li class="my-2 pl-2"><strong>Durability:</strong> Whether the publisher holds on to samples to give to subscribers who join after the publisher starts (&quot;Transient Local&quot;) or not (&quot;Volatile&quot;).</li>
<li class="my-2 pl-2"><strong>Deadline:</strong> The expected maximum amount of time between messages on a topic.</li>
<li class="my-2 pl-2"><strong>Lifespan:</strong> The amount of time until a message becomes stale. Messages which are published but not received until after their lifespan expires are dropped.</li>
<li class="my-2 pl-2"><strong>Liveliness:</strong> Whether publishers are implicitly considered alive if they publish to a topic (&quot;Automatic&quot;) or if they must manually use the publisher API to assert liveliness (&quot;Manual By Topic&quot;).</li>
<li class="my-2 pl-2"><strong>Lease Duration:</strong> The amount of time between assertions of liveliness (see above) before a node is considered to have lost liveliness.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While subscribers and publishers use the same profiles, the work in slightly different ways. A subscriber&#x27;s profile is the <em>minimum</em> quality it&#x27;s willing to accept, and the publisher&#x27;s profile is the <em>maximum</em> quality it&#x27;s willing to provide. This makes a bit more sense when you look at examples:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Publisher</th><th class="border border-black p-2 dark:border-white">Subscriber</th><th class="border border-black p-2 dark:border-white">Communication</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">Reliable</td><td class="border border-gray-500 p-2">Reliable</td><td class="border border-gray-500 p-2">Reliable</td></tr><tr><td class="border border-gray-500 p-2">Reliable</td><td class="border border-gray-500 p-2">Best Effort</td><td class="border border-gray-500 p-2">Best Effort</td></tr><tr><td class="border border-gray-500 p-2">Best Effort</td><td class="border border-gray-500 p-2">Best Effort</td><td class="border border-gray-500 p-2">Best Effort</td></tr><tr><td class="border border-gray-500 p-2">Best Effort</td><td class="border border-gray-500 p-2">Reliable</td><td class="border border-gray-500 p-2"><em>(doesn&#x27;t work)</em></td></tr></tbody></table>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="zenoh">Zenoh</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What if, however, you weren&#x27;t at the whimsy of the DDS protocol at all? If you let go of the purism of the DDS standard and instead substituted a more efficient protocol which could better handle the congested, dynamic conditions of a lossy wireless network?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is where <a href="https://zenoh.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Zenoh</a> comes in. Zenoh is a pub/sub protocol designed with one goal: reduce network overhead as much as possible. Most importantly, it has plugins tailored at interoperating with existing DDS systems.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When working with ROS 2, Zenoh dramatically reduces the amount of discovery traffic generated (by <a href="https://zenoh.io/blog/2021-03-23-discovery/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">up to 97%</a>) compared to a traditional DDS middleware. We&#x27;ve also found it to be much better at communication in general even after the discovery state is finished. We&#x27;re not the only ones: an Indy Autonomous Challenge team <a href="https://zenoh.io/blog/2021-09-28-iac-experiences-from-the-trenches/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">made the same observations</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So how is Zenoh some kind of miracle for communication? It&#x27;s a well-designed protocol, but more importantly, it&#x27;s not held down by the restrictions of the DDS standard. DDS is based on a protocol <a href="https://web.archive.org/web/20110915193450/http://www.omg.org/news/meetings/GOV-WS/pr/rte-pres/ddsi-demo.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">designed in 2003</a>, and is designed foremost for interoperability, not efficiency. You can see this in the protocol&#x27;s design: instead of defining a standard byte order, it&#x27;s defined on each message based on a flags register; information about the DDS system vendor is sent with every packet; each node is required to maintain a full graph of the network; and entity IDs are duplicated between submessages in a packet instead of being sent once per packet.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">ROS 2 was initially pitched as a DDS-centric version of ROS, replacing the bespoke TCPROS with DDS as an open, standard communication protocol. However, frustrations with the complexity of the DDS implementations and their poor performance on lossy wireless networks motivated OpenRobotics to search for an alternate middleware. Unsurprisingly, <a href="https://discourse.ros.org/uploads/short-url/o9ihvSjCwB8LkzRklpKdeesRTDi.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">they chose Zenoh</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re reading this after May 23, 2024 (<a href="https://en.wikipedia.org/wiki/World_Turtle_Day" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">World Turtle Day</a>), ROS 2 <a href="https://docs.ros.org/en/jazzy/Releases/Release-Jazzy-Jalisco.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Jazzy Jalisco</a> will be out, and hopefully <a href="https://discourse.ros.org/t/ros-2-alternative-middleware-report/33771" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rmw_zenoh</code></a> with it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the meantime, we&#x27;re stuck running a DDS on each host and using plugins to bridge the gaps. The best approach <em>as of writing</em> is to use <a href="https://github.com/eclipse-zenoh/zenoh-plugin-ros2dds" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">zenoh-plugin-ros2dds</a>, setting <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ROS_DOMAIN_ID</code> to a different value on each host to prevent DDS communication over the network. Old tutorials used <a href="https://github.com/eclipse-zenoh/zenoh-plugin-dds" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">zenoh-plugin-dds</a>, which works for ROS 2 applications but doesn&#x27;t support ROS 2 tooling as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Zenoh ended up being the solution to our team&#x27;s DDS dilemma. We plan to run Zenoh at the 2024 University Rover Challenge.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="udp-vs-tcp">UDP vs TCP</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When running over a network, Zenoh can run over either TCP or UDP. Typically, TCP is usually chosen as a network protocol for its reliable transmission guarantees, so it may seem like a poor fit for a system where some topics only need best-effort transmission. We&#x27;ve found, however, that our system tends to work much better over TCP than UDP, and we believe this is due to the TCP congestion control mechanism.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">TCP uses a complex <a href="https://en.wikipedia.org/wiki/TCP_congestion_control" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">congestion control</a> algorithm to avoid sending too much traffic over a congested network and causing undesirable behavior. With our radio network, the constrained link is specifically between our two IP radios, Each radio has a full 100 Mbps link to its computer (either the Jetson on the rover or the laptop in the base station), so the computers running ROS have no direct knowledge of network conditions. Then, when naive UDP protocols like DDS send way too much traffic over the network, it causes packets to be dropped on the radios, and the only feedback each host receives is a lack of acknowledgement after a timeout. On the other hand, TCP congestion control minimizes packet drops in the system, causing more reliable transmission.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="ros2-sunburst"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ros2-sunburst</code></h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In true breq.dev tradition, this post leaves off with a tool I&#x27;ve written to help solve some tiny facet of this problem. In this case, it&#x27;s a bandwidth usage visualization tool for ROS 2 traffic.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a plot of our team&#x27;s bandwidth usage by topic:</p>
<iframe src="/plots/ros-bandwidth.html" class="w-full" height="750"></iframe>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In ROS, topics tend to follow a hierarchy structure: your robot&#x27;s drive stack might fall under <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/drive</code>, commands might be under <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/drive/cmd_vel</code>, etc. This structure isn&#x27;t inherent to the transport protocol in any sense, but it does help you reason about the parts of your system which are using the most bandwidth.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using this tool works as follows:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Capture a sample of DDS traffic over your network with Wireshark. (Ensure you include the initial discovery traffic -- otherwise there is no way to map DDS writer/reader IDs to ROS 2 topic names).</li>
<li class="my-2 pl-2">Run this script on your packet capture file.</li>
<li class="my-2 pl-2">View the output in the terminal (the top topic names by value) and in the plot (the starburst visualization).</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can grab the script from this <a href="https://gist.github.com/breqdev/6968528b947ab1b3ddcaf0b69bc64e12" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">gist</a>.</p>
</div>]]></description>
            <link>https://breq.dev/2024/05/17/dds</link>
            <guid isPermaLink="false">/2024/05/17/dds</guid>
            <category><![CDATA[ros]]></category>
            <category><![CDATA[networking]]></category>
            <pubDate>Fri, 17 May 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Radios for Robots]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Radios are often an afterthought when designing a robotics project. Few robots are self-contained: almost all need to communicate with some external system, be that a basic remote control or a complex offboard system for advanced processing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most useful robots also need to operate in an uncontrolled environment. A Roomba needs to connect to a customer&#x27;s Wi-Fi network, a self-driving car needs to operate reliably regardless of local RF conditions, and a photography drone can&#x27;t fall out of the sky if an FPV racer sets up shop at the next field over.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My experience with this is primarily in the space of wheeled, &quot;prototype-scale&quot; robots of varying sizes. There is much to consider when scaling up a system beyond one or two prototypes, and the math tends to work out differently for flying &#x27;bots compared to terrestrial ones. (And if what you&#x27;re building is a boat, good luck -- RF and water do not mix well, and the <a href="https://en.wikipedia.org/wiki/Fresnel_zone" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Fresnel Zone</a> is your enemy.)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hardware">Hardware</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Radio hardware is built for a wide range of applications, meaning it&#x27;s difficult to even enumerate the options available to you. Especially for a prototype or hobby project, expect to look in interesting places for what you need to make things work.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="consumer-grade-networking">Consumer-Grade Networking</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ve gotten quite good at building cheap radios to connect consumer devices over short distances, over Wi-Fi, Bluetooth, or other protocols. While these chips might have been designed for things like smart lightbulbs or wireless headphones, they&#x27;re often a great choice for short-range networks and can often provide substantial throughput.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="your-built-in-wi-fi">Your built-in Wi-Fi</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Sometimes, the best radio is the one you already have. If your project uses a recent Raspberry Pi model, and your laptop has a Wi-Fi chip, then you&#x27;re all set for short range communications!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With Wi-Fi, one device must be the <em>access point</em> and the rest are <em>stations</em> (clients). There are a few advantages of making your robot the access point:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">You don&#x27;t need to configure the robot with the details of your existing Wi-Fi network</li>
<li class="my-2 pl-2">You can operate the robot in areas without an existing network</li>
<li class="my-2 pl-2">You have full control over the channel on which the network operates</li>
<li class="my-2 pl-2">It&#x27;s easy to connect more devices (e.g., encouraging nontechnical users to connect their phones to try operating your creation!)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a great option for running your robot at events where you won&#x27;t know the setup beforehand, provided you don&#x27;t expect too many other robots taking up their slice of the 2.4 GHz spectrum.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">But it does come with a major limitation: It is generally quite difficult to create a setup in which your robot, as the access point, is able to access the internet via one of its clients. If your laptop is a station on your robot&#x27;s network, then whoops, it can&#x27;t be on your home network anymore. Even if you plug your laptop into an Ethernet connection with internet access, good luck getting your laptop to route traffic from your robot to the Internet and back. It can be done, but it takes some complicated network configuration that isn&#x27;t fun to deal with.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="bluetooth">Bluetooth</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bluetooth is great as a &quot;it just works&quot; system, requiring just the initial pairing flow before you&#x27;re off to the races. I used it to send commands to the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/outshine">LED choker</a> that I made. You can get up and running quickly with a microprocessor with Bluetooth support (like the <a href="https://www.adafruit.com/product/4062" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nRF52480</a>) or a drop-in Bluetooth Serial module (like the <a href="https://www.amazon.com/HiLetgo-Wireless-Bluetooth-Transceiver-Arduino/dp/B071YJG8DR" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">HC-05</a> available from tons of sketchy Amazon sellers).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bluetooth modules are typically much lower power than their Wi-Fi counterparts, don&#x27;t usually work in a scenario more complex than a point-to-point connection, and have substantially worse throughput. Different Bluetooth profiles support sending different amounts of throughput through, but something like the <a href="https://www.bluetooth.com/specifications/specs/serial-port-profile-1-1/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Serial Port Profile</a> will struggle to transmit images or video.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bluetooth also doesn&#x27;t typically use the TCP/IP stack (although it can be done with a specific profile). This is typically fine for microcontroller-based robots, but can make development annoying if your robot runs Linux.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="external-wi-fi-routers-and-cards">External Wi-Fi routers and cards</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Still within the consumer realm, some off-the-shelf Wi-Fi routers and cards can be great for getting a bit more performance or range out of a Wi-Fi based system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Consider a Wi-Fi router on your robot as an extension to the &quot;robot as access point&quot; idea in the prior sections. You get a physical piece of hardware with a sole purpose of providing a Wi-Fi network, along with Ethernet ports to connect any computers onboard your robot. Wi-Fi routers typically use higher power, have better antennas, and can provide more throughput than (ab)using your device&#x27;s built-in radio.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Alternatively, you can put the router at a base station and connect it (e.g. via Ethernet) to give it Internet access. This gives you the benefits of a wireless network entirely under your control while still giving your robot and ground station Internet access for development.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the station end, you can still improve compared to whatever built-in adapter you have. Some UAV enthusiasts working on the <a href="https://github.com/OpenHD/OpenHD" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OpenHD project</a> have identified the <a href="https://openhd.gitbook.io/open-hd/hardware/wifi-adapters" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Realtek RTL8812AU and friends</a> to be a strong contender, having the maximum allowed 500mW power output, two antennas for diversity and MIMO operation, and support for both the 2.4 GHz and 5.8 GHz bands (which we&#x27;ll discuss more in the <em>Band Planning</em> section).</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="esp32-chips">ESP32 chips</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Back in my day, all we had was the ESP8266, a cheap board with no GPIO, bad hardware support, and acceptable 2.4 GHz Wi-Fi in a very cheap package. Nowadays, Espressif Systems has unveiled a line of ESP32 boards with ample GPIO, fast clock speeds, and both Wi-Fi and Bluetooth capabilities.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Choosing an ESP32-based design gives you lots of options for small robots, either as the robot&#x27;s primary microcontroller or as a peripheral. For instance, you can use Bluetooth to set up the initial connection, then let the user input their Wi-Fi details and switch to that connection. Or, you can use Bluetooth for telemetry and controls, while keeping a Wi-Fi link during development for stronger visibility.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, this series of chips is in the microcontroller realm, so they won&#x27;t be able to push through as much throughput as a dedicated Wi-Fi router or card. for small and short-range projects, however, they can be a strong contender.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="drones">Drones</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The world of UAVs has brought forth many unique solutions for radio communication. Unlike wheeled robots, you can typically assume that a drone will maintain line-of-sight with the control station at all times, giving signal penetration a lower priority. Drones also are extremely sensitive to weight, so solutions tend to be compact and simple.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One quirk of drone communications is that drones typically use two frequencies: one for control and basic telemetry, and another for FPV/video streaming, leading to two very different sets of solutions.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For controls, the <a href="https://rfdesign.com.au/products/rfd900-modem/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RFD900</a> is by far the most well-known choice. It provides a basic serial connection which typically facilitates the transfer of <a href="https://mavlink.io/en/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mavlink</a> messages. Unlike any of the other solutions we&#x27;ve looked at thus far, the RFD900 works on the 900 MHz band.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The RFD900 uses <em>frequency-hopping spread spectrum</em>: the specific frequency used will jump around in the 900 MHz band to avoid interference. This is great in a scenario with other RFD900s (like an FPV drone racing event) since they&#x27;ll generally stay out of each other&#x27;s way.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">FPV drones also typically use <em>analog</em> video systems. There are a few reasons why these have stuck around, even though they use spectrum far less efficiently than digital systems:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Since digital systems need to perform video encoding, analog video requires much less hardware, saving weight</li>
<li class="my-2 pl-2">Analog video tends to degrade gracefully under interference or poor signal conditions</li>
<li class="my-2 pl-2">Since drones typically operate under line-of-sight conditions, they can use higher bands (most commonly, 5.8 GHz) which allow for wider channels</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because of this, analog video gear is generally commonly available. If you&#x27;re working with a small robot with only a single camera over a relatively short distance, this can be a good solution. However, note that you can&#x27;t perform any onboard processing with such a camera.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wisp-gear">WISP Gear</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Wireless Internet Service Providers, or WISPs, are companies that provide Internet access to customers using wireless links as opposed to wired (fiber or copper) connections. Equipment made for WISPs is built to be reliable, designed for long-term operation, highly configurable, and to operate right up to the power limits allowed by law.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ubquiti products are cheap and work well, including the <a href="https://store.ui.com/us/en/products/rocketm2" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Rocket</a> and <a href="https://store.ui.com/us/en/products/bulletm2-hp" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Bullet</a> series of radios. Each line comes in 2 GHz and 5 GHz variants, with 900 MHz variants discontinued but often found on eBay.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For stronger 900 MHz capabilities, consider the Cambium <a href="https://www.cambiumnetworks.com/products/backhaul/ptp-450-900-mhz/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PTP 450 900</a>. Note that these use a proprietary protocol. While Cambium makes some other variants, they&#x27;re either difficult to get licenses for (like the PTP 450 on 3.65 GHz) or prohibitively expensive for most use cases (like the PMP line, which supports point-to-multipoint scenarios across many band types).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These radios provide an Ethernet port and can effectively make your network look like a flat Ethernet subnet, greatly simplifying your configuration. Alternatively, many Ubiquiti radios can be configured to operate as a standards-compliant Wi-Fi hotspot instead of in point-to-point mode, allowing you to combine a high-powered WISP radio with inexpensive consumer Wi-Fi gear.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="packet-radio">Packet Radio</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A promising method I&#x27;ve been looking for an excuse to use is <a href="https://learn.adafruit.com/adafruit-rfm69hcw-and-rfm96-rfm95-rfm98-lora-packet-padio-breakouts" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">low-cost packet radio modules</a>. These implement a barebones protocol without any sort of pairing, connection, or scanning logic. They typically support relatively slow data rates, but can cover a relatively large area.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These radios tend to transmit in one of two bands:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">433 MHz: Generally unlicensed in Europe, licensed in the Americas (although usable with an Amateur Radio license).</li>
<li class="my-2 pl-2">900 MHz: Unlicensed in both Europe and the Americas (albeit slightly different bands, 868 vs 915 depending on region). This can be a blessing (it&#x27;s easier to get started with) or a curse (in the US/Canada, there are many more devices in this band than in 433).</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An iteration of these uses <a href="https://lora-alliance.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">LoRa</a>, a modulation technique that enables much farther communication (although this increases cost).</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="amateur-hardware">Amateur Hardware</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another source of radios is hardware designed for amateur radio use. These operate on a huge range of bands, but typically focus on voice transmissions. The digital modes that do exist are intended for short, infrequent messages. This is typically not a good fit for a robotics scenario, but it could be useful.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the absolute low end, you can always use a cheap <a href="https://www.baofengradio.com/products/uv-5r" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Baofeng radio</a> (usually ~$20 USD on Amazon) with your computer&#x27;s <a href="https://digirig.net/product/baofeng-direct-to-computer-audio-cable/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">sound card</a> and some software for a basic digital mode.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, amateur hardware also has restrictions (e.g., no commercial use) which we&#x27;ll talk about more when we discuss amateur licensing.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="band-planning">Band Planning</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When picking a radio, you&#x27;ll often want to consider the frequency band it transmits on. Specific bands have certain innate characteristics which make them more or less suited to a given application. You&#x27;ll also need to consider other radio systems within your robot and ensure you do not cause interference.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Different bands have different strengths and weaknesses. Generally, lower frequency bands will provide better signal penetration and non-line-of-sight operation (e.g., operating your robot from the other side of a wall), while higher frequencies are better suited to line-of-sight or short-range uses.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An advantage of higher-frequency bands is that they generally can provide much higher bandwidth, both via higher signal rates and wider channels. If your system involves streaming lots of high-resolution video, point clouds, etc., you will probably struggle on a lower-frequency setup.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If your robot has multiple communication systems, you&#x27;ll need to make sure you aren&#x27;t causing interference to yourself. This could take the form of multiple links to your base station (such as separate links for telemetry and video), or it could arise in a system with multiple interacting robots.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Generally, for multiple radio links to your base station, you will want to put each link on a separate band to minimize interference. For instance, many drone pilots use 900 MHz or 2.4 GHz for teleoperation and 5.8 GHz for video streaming.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Interference can also be tricky to identify the source of. On the Rover team, we noticed an issue with our 900 MHz radio gear interfering with our high-precision GPS receivers, preventing us from attaining a precise fix.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Your robot might also need to operate in an area with lots of interference, such as a Maker Faire, in which the sheer number of users on the 2.4 GHz and 5 GHz bands can make protocols like Wi-Fi almost unusable. While <a href="https://jpieper.com/2020/04/01/spread-spectrum-rf-control-and-telemetry/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">frequency-hopping protocols</a> can work more reliably, they still have their limitations.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="amateur-licensing">Amateur Licensing</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re a hobbyist, you aren&#x27;t just limited to the ISM or unlicensed bands: the Amateur Radio Service is there for you! Getting an amateur license gives you access to a much larger array of bands across a wide range of radio spectrum. While this varies by country, most countries do something similar to the U.S.:</p>
<div class="contents break-inside-avoid print:block"><img alt="Band chart image created by the ARRL" loading="lazy" width="1009" height="768" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Famateur-band-chart.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Famateur-band-chart.jpg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Famateur-band-chart.jpg&amp;w=2048&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The most important restrictions to keep in mind are:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Your use-case must be for a non-commercial purpose.</li>
<li class="my-2 pl-2">You generally cannot use encryption (although there&#x27;s some <a href="https://www.qsl.net/kb9mwr/projects/wireless/Data%20Encryption%20is%20Legal.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">debate</a> on this). You definitely can&#x27;t use encryption to hide the content of your messages.</li>
<li class="my-2 pl-2">Any data protocol you use must be publicly documented.</li>
<li class="my-2 pl-2">You need to obtain an amateur license, and you must identify with your callsign once every 10 minutes during transmission.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Getting licensed is generally straightforward (<a href="https://beckystern.com/2020/03/08/my-experience-getting-licensed-in-ham-radio/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Becky Stern&#x27;s article</a> gives a good overview of the process). A few tips from my own journey:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">If all you care about is passing the test, just go on <a href="https://hamstudy.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">hamstudy.org</a> and memorize the questions for a few hours. There are resources that you can pay for to learn the concepts in more detail, but it&#x27;s not necessary.</li>
<li class="my-2 pl-2">You&#x27;ll initially be assigned a random callsign, but you can <a href="http://www.arrl.org/applying-for-a-vanity-call" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">apply for a vanity call</a> immediately afterwards for a small fee.</li>
<li class="my-2 pl-2">If you&#x27;re a college student, see if there&#x27;s a club on campus that runs exams!</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the U.S., there are a few classes of license, and most countries follow a similar structure. At time of writing, I hold a technician class license, and it gives me almost all of the same privileges as the higher classes on the shorter bands (433 MHz and up).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Some amateur bands overlap with unlicensed/ISM bands: the 33 centimeter amateur band takes the same space as the 915 MHz ISM band, the 2.4 GHz amateur band partially overlaps the ISM one, and the 5.8 GHz amateur band is a subset of the spectrum set aside for Wi-Fi. In these bands, you can often <em>use commercial grade hardware at higher power limits or with larger antennas</em> -- for instance, running a Ubiquiti access point on 2412 MHz at maximum power with a high-gain antenna. This gear generally isn&#x27;t designed for use above the unlicensed limits, so you won&#x27;t always gain a significant boost, but it can still give you a bit more range.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Other bands are generally pretty quiet except for amateur users:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The 2 meter and 70 centimeter bands are predominantly used for analog FM voice, although the 70 cm band overlaps with the unlicensed 433 MHz band in Europe (meaning you can find consumer hardware which operates on it)</li>
<li class="my-2 pl-2">The 23 centimeter (1240 MHz) band is generally pretty empty, although it is right next to the frequencies used by GNSS (e.g., GPS) receivers which can cause problems, and it&#x27;s tough to find hardware for</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Based on my experience so far, I&#x27;ve developed a few rules for selecting which system to use:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Start with Wi-Fi.</strong> It covers a surprising number of use cases regarding range and data throughput, and hardware is common, cheap, and lightweight.</li>
<li class="my-2 pl-2"><strong>Punch through obstacles using either higher power or larger wavelength.</strong> If you&#x27;re going a significant distance through a forest, for instance, you&#x27;ll probably struggle with consumer Wi-Fi modules. You could switch to a larger wavelength like 900 MHz, which can <a href="https://www.reddit.com/r/fpv/comments/193tgt/fpv_live_comparison_of_58ghz_vs_900mhz/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">better penetrate obstacles</a>, or you can stay on 2.4 GHz but switch to following amateur regulations and using WISP gear and high-gain antennas.</li>
<li class="my-2 pl-2"><strong>Consider more specialized systems if needed.</strong> You might struggle to find components to run on other bands or using different protocols, but it&#x27;s sometimes the only way to satisfy an unusual use case.</li>
</ul></div></div>]]></description>
            <link>https://breq.dev/2024/04/16/radios</link>
            <guid isPermaLink="false">/2024/04/16/radios</guid>
            <category><![CDATA[robotics]]></category>
            <category><![CDATA[networking]]></category>
            <pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[88x31 Buttons and Network Science]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="background">Background</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You might&#x27;ve noticed the tiny buttons/badges at the footer of this site and others, each showing a pixel-art design and linking to another website. These are called <em>88x31 buttons</em>, and they&#x27;re a de-facto standard of the indie web. Sources on this are few and far between, but <a href="https://neonaut.neocities.org/cyber/88x31" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">The Neonauticon</a> identified the start to be Netscape, which published &quot;Netscape Now&quot; buttons that sites could add to show their use for then-new web features.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">People took this idea and ran with it, placing 88x31 buttons for themselves, their friends, and projects they supported in their websites, forum signatures, etc. Similar to <a href="https://en.wikipedia.org/wiki/Webring" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">webrings</a>, 88x31 buttons provided a fun way for people to find related content to a given page.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friends and I are working on <a href="https://eightyeightthirty.one/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">mapping the entire 88x31 graph</a>. The result of our work is the first ever snapshot of the entire 88x31 network, available in <a href="https://eightyeightthirty.one/graph.json" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">a single JSON file</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Relatedly, a few semesters ago, I took a course on <a href="https://en.wikipedia.org/wiki/Network_science" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">network science</a>. One of the main takeaways was that lots of real-world networks exhibit something called the <a href="https://en.wikipedia.org/wiki/Small-world_network" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><em>small-world property</em></a>, specifically:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">High <em>clustering</em> (you are more likely to be friends with your friends&#x27; friends)</li>
<li class="my-2 pl-2">Low <em>distance</em> (the typical distance between two randomly-chosen nodes stays small as the graph grows)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We see this phenomenon across all sorts of network types: the original 1998 <a href="https://edisciplinas.usp.br/pluginfile.php/4205021/mod_resource/content/1/NAture_Watts_Strogatz.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Watts &amp; Strogatz paper</a> uses networks of film actors, the electrical grid, and the neural network of a worm.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So: we&#x27;ve got a brand new dataset and some criteria to test. Does the 88x31 graph exhibit the small-worlds property?</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="analysis">Analysis</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To analyze the behavior of the graph as it expands, I&#x27;ll be using two datasets: one with 4428 nodes that we obtained partway through the scraping work, and the most recent dataset with 16023 nodes. Analysis was done using <a href="https://gephi.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Gephi</a>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="clustering">Clustering</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Clustering is the measure of how locally-connected a graph is. Think about a social graph: if everyone had a set of, say, 20 friends randomly chosen from a group of 1 million, you&#x27;d have quite low clustering. However, typically, you&#x27;re much more likely to be friends with your friends&#x27; friends, implying high clustering. Clustering is quantified by the <em>clustering coefficient</em>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The formal definition of a graph is a set of vertices (nodes) <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span></span> and a set of edges (links) <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi></mrow><annotation encoding="application/x-tex">E</annotation></semantics></math></span></span>, such that:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>G</mi><mo>=</mo><mo stretchy="false">(</mo><mi>V</mi><mo separator="true">,</mo><mi>E</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">G = (V, E)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We write that edge <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>e</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub></mrow><annotation encoding="application/x-tex">e_{ij}</annotation></semantics></math></span></span> connects vertex <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>v</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">v_i</annotation></semantics></math></span></span> with vertex <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>v</mi><mi>j</mi></msub></mrow><annotation encoding="application/x-tex">v_j</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To define clustering, it&#x27;s useful to define the <em>neighborhood</em> of a vertex <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>N</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">N_i</annotation></semantics></math></span></span> as the set of all of its connected neighbors, considering both links &quot;in&quot; and links &quot;out&quot;:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>N</mi><mi>i</mi></msub><mo>=</mo><mo stretchy="false">{</mo><msub><mi>v</mi><mi>j</mi></msub><mo>:</mo><msub><mi>e</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mo>∈</mo><mi>E</mi><mo>∨</mo><msub><mi>e</mi><mrow><mi>j</mi><mi>i</mi></mrow></msub><mo>∈</mo><mi>E</mi><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">N_i = \{v_j : e_{ij} \in E \vee e_{ji} \in E \}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s go a step further and define the <em>degree</em> of a vertex <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>k</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">k_i</annotation></semantics></math></span></span> as the number of connected neighbors it has, i.e.,</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>k</mi><mi>i</mi></msub><mo>=</mo><mi mathvariant="normal">∣</mi><msub><mi>N</mi><mi>i</mi></msub><mi mathvariant="normal">∣</mi></mrow><annotation encoding="application/x-tex">k_i = |N_i|</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">where we use the vertical bars to mean the cardinality (number of items contained within) the set of neighbors.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">How many links <em>could</em> exist within the neighborhood of a node? Let&#x27;s ignore self-loops, so the number of possible links is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>k</mi><mi>i</mi></msub><mo>⋅</mo><mo stretchy="false">(</mo><msub><mi>k</mi><mi>i</mi></msub><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">k_i \cdot (k_i - 1)</annotation></semantics></math></span></span> -- each node can connect to each of the other nodes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The number of links <em>actually</em> in the neighborhood can be written as:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo stretchy="false">{</mo><msub><mi>e</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><mo>:</mo><msub><mi>v</mi><mi>j</mi></msub><mo separator="true">,</mo><msub><mi>v</mi><mi>k</mi></msub><mo>∈</mo><msub><mi>N</mi><mi>i</mi></msub><mo separator="true">,</mo><msub><mi>e</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><mo>∈</mo><mi>E</mi><mo stretchy="false">}</mo></mrow><annotation encoding="application/x-tex">\{ e_{jk} : v_j, v_k \in N_i, e_{jk} \in E \}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And thus, the clustering coefficient for a given node is:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>C</mi><mi>i</mi></msub><mo>=</mo><mfrac><mrow><mi mathvariant="normal">∣</mi><mo stretchy="false">{</mo><msub><mi>e</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><mo>:</mo><msub><mi>v</mi><mi>j</mi></msub><mo separator="true">,</mo><msub><mi>v</mi><mi>k</mi></msub><mo>∈</mo><msub><mi>N</mi><mi>i</mi></msub><mo separator="true">,</mo><msub><mi>e</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><mo>∈</mo><mi>E</mi><mo stretchy="false">}</mo><mi mathvariant="normal">∣</mi></mrow><mrow><msub><mi>k</mi><mi>i</mi></msub><mo stretchy="false">(</mo><msub><mi>k</mi><mi>i</mi></msub><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">C_i = \frac{|\{ e_{jk} : v_j, v_k \in N_i, e_{jk} \in E \}|}{k_i(k_i-1)}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can use software to compute the clustering for each node, and take the average across all of them. The average clustering coefficient is:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Nodes</th><th class="border border-black p-2 dark:border-white">Clustering Coefficient</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">4428</td><td class="border border-gray-500 p-2">0.122</td></tr><tr><td class="border border-gray-500 p-2">16023</td><td class="border border-gray-500 p-2">0.123</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Is this value high enough to indicate high clustering? For comparison, let&#x27;s construct two random graphs with the same number of nodes and edges as each of our sample datasets: one with 4428 nodes and 17254 edges, and one with 16023 nodes and 57202 edges. Gephi supports the <a href="https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93R%C3%A9nyi_model" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Erdős–Rényi model</a>, which generates a graph based on the number of nodes and the probability of an edge between any two given nodes. Based on the real graph, we can determine the probability numbers as:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>17254</mn><mrow><mn>4428</mn><mo>⋅</mo><mn>4427</mn></mrow></mfrac><mo>=</mo><mn>0.0008802</mn></mrow><annotation encoding="application/x-tex">\frac{17254}{4428 \cdot 4427} = 0.0008802</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>57202</mn><mrow><mn>1602</mn><msup><mn>3</mn><mn>2</mn></msup></mrow></mfrac><mo>=</mo><mn>0.0002228</mn></mrow><annotation encoding="application/x-tex">\frac{57202}{16023^2} = 0.0002228</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I needed to multiply these by 2 to get them to load into Gephi properly; perhaps there is some correction going on for undirected vs directed graphs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, we can get the average clustering coefficient for each of these graphs:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Nodes</th><th class="border border-black p-2 dark:border-white">Clustering (88x31)</th><th class="border border-black p-2 dark:border-white">Clustering (Random)</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">4428</td><td class="border border-gray-500 p-2">0.122</td><td class="border border-gray-500 p-2">0.001</td></tr><tr><td class="border border-gray-500 p-2">16023</td><td class="border border-gray-500 p-2">0.123</td><td class="border border-gray-500 p-2">0.000</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Note that the precision of these values is limited by the output of Gephi to 3 decimal places -- those values really are that low!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our random graph has low (sometimes called <em>vanishing</em>) clustering as the number of nodes increases. This is the typical behavior for a random graph to have -- as graphs grow, the likelihood of a given node being connected to another node decreases, regardless if those nodes are linked by an intermediate node.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In contrast, our 88x31 graph has high (or <em>nonvanishing</em>) clustering even as the number of nodes increases. This is a notable property in and of itself, and brings us halfway to demonstrating the small-worlds property!</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="distance">Distance</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <em>average path length</em> <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span></span> of a graph is the average of the length of the shortest path between each pair of nodes, ignoring node pairs with no connecting path.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can compute the average path length of our graph to be:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Nodes (<span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span>)</th><th class="border border-black p-2 dark:border-white"><span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>log</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log(N)</annotation></semantics></math></span></span></th><th class="border border-black p-2 dark:border-white">Avg. Path Length <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span></span></th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">4428</td><td class="border border-gray-500 p-2">8.396</td><td class="border border-gray-500 p-2">7.182</td></tr><tr><td class="border border-gray-500 p-2">16023</td><td class="border border-gray-500 p-2">9.681</td><td class="border border-gray-500 p-2">11.269</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the network to exhibit the small-worlds property, we need the average path length to be proportional to the logarithm of the number of nodes -- that&#x27;s why I&#x27;ve added it to the table above.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Between graphs, the average path length increased by a factor of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1.57</mn></mrow><annotation encoding="application/x-tex">1.57</annotation></semantics></math></span></span>, while the number of nodes increased by a factor of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3.62</mn></mrow><annotation encoding="application/x-tex">3.62</annotation></semantics></math></span></span> (and its logarithm increased by <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1.15</mn></mrow><annotation encoding="application/x-tex">1.15</annotation></semantics></math></span></span>). This isn&#x27;t exactly on the expected value for <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi><mo>∝</mo><mi>log</mi><mo>⁡</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">L \propto \log N</annotation></semantics></math></span></span> (where <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo>∝</mo></mrow><annotation encoding="application/x-tex">\propto</annotation></semantics></math></span></span> means &quot;proportional to&quot;), but it&#x27;s also quite clearly less than the amount that <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> increased, meaning <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi><mo>≪</mo><mi>N</mi></mrow><annotation encoding="application/x-tex">L \ll N</annotation></semantics></math></span></span>. Personally, I&#x27;m comfortable calling this bound satisfied.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few reasons come to mind as to why we might not be getting the expected value here:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> is small enough that this is just noise in the dataset. 16K nodes isn&#x27;t <em>that</em> big.</li>
<li class="my-2 pl-2">The topology of the two 88x31 graphs is different. The first graph consists of the nodes discovered off of <a href="https://notnite.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">notnite.com</a> in a few hours, while the second graph is the entire network as identified by our scrapers. The first graph is going to skew towards the English-speaking, indie web, and LGBT communities, while the second graph includes more Italian and Brazilian communities, tabletop RPG forums, and other groups which are broadly separate from the cluster in which the scraper started. Qualitatively, these other groups seem less interconnected compared to the initial dataset.</li>
<li class="my-2 pl-2">The second graph has a slightly different scraper implementation which analyses the HTML statically instead of using a webdriver to load the page, potentially missing more links on sites which use client-side rendering techniques.</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hubs">Hubs</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Real-world graphs often have <em>hubs</em>. In a transportation network, hubs are points with connections to lots of other places; in the Internet, hubs are major ASNs like Hurricane Electric; in a social graph, hubs are just people who know a lot of other people.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can see if our 88x31 graph exhibits this same behavior by looking at the <em>degree distribution</em> of the graph. For this, I generated the degree of each node in the full 88x31 graph in Gephi, then exported the table into Python to take a histogram and then into Google Sheets to make charts.</p>
<img class="mx-auto" src="/images/eightyeightthirtyone/distribution.svg" alt="" width="600" height="371"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Above is a log-log plot of the degree distribution of the full 88x31 graph. Let&#x27;s walk through it, since it&#x27;s not the easiest thing to digest.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each x-value represents a degree which a node could have, and the corresponding y-value is the number of nodes with that degree. Per the chart, there are about 10,000 nodes with degree 1, but closer to 500 with degree 10, and only a handful with degree 100.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The &quot;clumping&quot; taking place at the bottom is since we&#x27;re working with a discrete number of nodes. For each of the higher degrees, we&#x27;ll probably only see only 1 or 2 nodes with that degree, and whether it&#x27;s 1 node or 2 nodes is up to random chance. Since there&#x27;s nothing in between, we see those two lines at the bottom of the graph, and we can notice quite a bit more noise there as well. Similarly, towards the top-left of the graph, we&#x27;re running into quantization there as the degree of a node must be a whole number.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So what are we looking for here? For reasons which will become clear, let&#x27;s bring back our random graph from earlier and run the same procedure on it:</p>
<img class="mx-auto" src="/images/eightyeightthirtyone/distribution-with-random.svg" alt="" width="600" height="371"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That looks very different -- sort of like a bell curve around some single most-popular degree value, giving the network a sense of &quot;scale&quot; or &quot;size.&quot; The 88x31 graph, on the other hand, is <a href="https://en.wikipedia.org/wiki/Scale-free_network" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><em>scale-free</em></a> in that hubs in the network get larger as the network grows.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Scale-free networks are more rigorously defined as a network in which the degree distribution follows a <a href="https://en.wikipedia.org/wiki/Power_law" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><em>power law</em></a>. That blue line in the graph is the power law function that best fits the data. Especially compared to the random graph, it&#x27;s pretty clear that our 88x31 graph meets the definition of scale-free.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What are these hubs in our network? In our case, they&#x27;re mostly sites which try to collect as many 88x31 buttons as possible into a single site, like the <a href="https://neonaut.neocities.org/cyber/88x31" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">neonaut collection</a> (which currently holds the #1 spot).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusions">Conclusions</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this post, we looked at an entirely new network dataset through the lens of network science, discovering that it fits the same criteria that characterize social, communication, transport, and biological networks in our world.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Network science is a field that interests me greatly, but that doesn&#x27;t often come up much in my work, so I&#x27;m grateful for the chance to work on this project. A lot of key insights about real-world networks from the field come up in unexpected ways.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ll continue to track the 88x31 network over the forseeable future. Maybe we&#x27;ll discover an unmapped part of the network and end up with even more data to sort through. If you&#x27;d like to follow the technical effort, feel free to watch or engage via the project&#x27;s <a href="https://github.com/NotNite/eightyeightthirtyone" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GitHub repository</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, you can join the network as well, and it&#x27;s as straightforward as it sounds: find a friend with 88x31s on their website, make up your own little 88 pixel by 31 pixel image in your favorite bitmap image editor, and get them to add it to their site along with a link to your website! A few tips:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://www.gimp.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GIMP</a> is a great editor to get started with -- just crank the zoom up to 800% or so depending on your screen size.</li>
<li class="my-2 pl-2">Save as a PNG (or GIF for animated 88x31s) to prevent lossy compression from messing with your pixel art.</li>
<li class="my-2 pl-2">When adding a button to your website, use the CSS <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">image-rendering: pixelated</code> property to keep your pixel-art goodness from being blurred on high-DPI displays or when zoomed in.</li>
<li class="my-2 pl-2">Look for inspiration in the buttons made by your friends, and don&#x27;t be afraid to experiment!</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Hope to see you on <a href="https://eightyeightthirty.one/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">eightyeightthirty.one</a> soon!</p></div>]]></description>
            <link>https://breq.dev/2023/12/26/88x31-science</link>
            <guid isPermaLink="false">/2023/12/26/88x31-science</guid>
            <category><![CDATA[88x31]]></category>
            <category><![CDATA[research]]></category>
            <pubDate>Tue, 26 Dec 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[eightyeightthirty.one]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2256" height="1924" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Feightyeightthirtyone%2Fgraph.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Feightyeightthirtyone%2Fgraph.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The entire mapped network of 16,000+ pages, as of 2023-12-26.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project was a joint effort by myself and a few friends:</p>
<div class="flex justify-center" style="image-rendering:pixelated"><div class="flex flex-row border-2 border-black dark:border-white p-4 gap-4 rounded-lg"><a href="https://notnite.com/"><img src="/badges/notnite.png"/></a><a href="https://adryd.com/"><img src="/badges/adryd.png"/></a><a href="https://breq.dev/"><img src="/badges/breq.png"/></a></div></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">88x31 buttons are everywhere on the indie web -- they&#x27;re those tiny buttons on the homepage or footer of sites like mine which link to friends, projects, etc. They&#x27;ve been around for decades and have spread all over webpages and forum signatures. However, until now, there has been no way to view the entire network of 88x31 links all at once.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My friends and I have implemented a scraper which can crawl a page for 88x31 links, a server to manage the queue of sites to crawl, and a web frontend to visualize the graph as a whole.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://notnite.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NotNite</a> provided the initial idea and implementation, and after <a href="https://adryd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">adryd</a> sent me a proof of concept, the three of us and some friends immediately hopped on a call to work out the details.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The implementation consists of three parts: the scraper, the orchestration server, and the frontend.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="scraper">Scraper</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The scraper receives a URL and is responsible for visiting the webpage to look for 88x31s which link to pages. We experimented with a webdriver-based solution (using <a href="https://pptr.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Puppeteer</a>), but ended up switching to static HTML parsing for performance, making the tradeoff that the scraper can&#x27;t read client-side-rendered pages.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The scraper is also responsible for noting any redirects that happen, and for trying to identify canonical URLs for pages, just as a traditional search engine crawler would need to.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The scraper is written in Rust and keeps no state, so it can be scaled up horizontally as needed.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="orchestration-server">Orchestration Server</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To coordinate the scrapers and provide access control, an orchestration server is used. This server also accepts URLs into the network (adding them to the queue) and produces the graph file.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The orchestration server is backed by a Redis database -- this was chosen to ensure the queue was stored in memory.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The orchestration server also handles inserting found links into the queue, and correcting records after a redirect is found -- both complex processes to handle edge cases that arise when webmasters change things about their site.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="frontend">Frontend</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The frontend is used to display the graph to the user and allow them to navigate it efficiently. It has some functionality for zooming to a particular node and highlighting its links, and it can show the badge images used to link from one site to another.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We struggled to choose an effective graph library implementation which could render this many nodes on the screen, but eventually settled on <a href="https://cosmograph.app/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Cosmograph</a> as it blew everything else out of the water in terms of performance. The end result still takes time to load the initial graph but feels relatively snappy to navigate even on mobile devices.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The result is a map of over 16,000 pages, either linking to or linked from another using 88x31s.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One thing that we didn&#x27;t expect to happen was webmasters noticing our user-agent in their access logs and inquiring about our work. Apparently folks aren&#x27;t used to scrapers which specifically target 88x31 images! It was very cool to get to explain this project to those who asked about it, and we made sure to clearly identify ourselves and provide opt-out instructions in case any operators disagreed with our mission.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also got to finally use everything I learned in a network science course last year to <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2023/12/26/88x31-science">analyze our resulting data</a>, which was quite fun. TL;DR: our dataset of 88x31 links is similar to other social networks which have been studied in the field!</p></div>]]></description>
            <link>https://breq.dev/projects/eightyeightthirtyone</link>
            <guid isPermaLink="false">/projects/eightyeightthirtyone</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[88x31]]></category>
            <category><![CDATA[web]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[rust]]></category>
            <pubDate>Tue, 26 Dec 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[In Defense of Really Long Merge Requests]]></title>
            <description><![CDATA[<div class="e-content font-body"><blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Disclaimer: This article may or may not be an application of <a href="https://meta.wikimedia.org/wiki/Cunningham%27s_Law" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Cunningham&#x27;s Law</a>. I&#x27;d love to hear about how other teams work and why people like (or dislike!) their current way of doing things.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve maintained an (albeit small) <a href="https://github.com/breqdev/flask-discord-interactions/pulls?q=is%3Apr+is%3Aclosed" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">open-source library</a>, led software development on a <a href="https://www.northeasternrover.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">robotics team of &gt;40 people</a>, and worked on a professional software team at co-op. Through this, I&#x27;ve gotten to experience a range of code review styles and merge request etiquette. A common piece of wisdom I&#x27;ve seen tends to be: &quot;keep your merge requests small, so they are easily reviewable.&quot; Conventional wisdom states that longer merge requests are more difficult to review -- obviously this is true, since there are more changes to look through. I&#x27;d like to argue, however, that <em>in the long run</em>, writing longer merge requests can save time and effort for a team.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="breaking-features-down-is-burning-developer-effort">Breaking features down is burning developer effort</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Suppose you&#x27;ve set out to implement a feature, and you&#x27;ve ended up with a much larger implementation than you expected. Should you split that implementation into smaller merge requests to make life easier for your reviewers?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What makes breaking up merge requests tricky is that <strong>you&#x27;ll need to verify that the state of the repository in between your two merge requests is valid.</strong> You need to create an intermediate version, then test that intermediate version. Semantically, this &quot;in-between&quot; version might make no sense, since it contains a partially-completed feature.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re using a tool like Rust&#x27;s <a href="https://doc.rust-lang.org/nightly/clippy/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">clippy</a> to analyze your code, the intermediate version might need to be littered with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">#[allow(dead_code)]</code> or your language&#x27;s equivalent in order to pass CI. This clutters the commit history and provides no real value. And what if you accidentally leave in one of those <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">#[allow(dead_code)]</code> functions later?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Splitting a merge request into more than two parts makes this even more complicated. In many cases, it just isn&#x27;t practical to break a single feature into multiple semantically reasonable and CI-passing intermediate steps.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="more-frequent-code-reviews-means-more-round-trips-and-greatly-reduced-speed">More frequent code reviews means more &quot;round trips&quot; and greatly reduced speed</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When you submit a merge request, you&#x27;re essentially handing things off to your reviewer. A good reviewer will get back to you in 24 hours, but someone who&#x27;s busy with other work might take even longer. In this intermediate time, what do you do? In most cases, you&#x27;ll end up switching to another project, losing steam on the feature you were working on. Only when they get back to you can you resume work on the feature. The more of these &quot;round trips&quot; you have to deal with, the longer it will take you to implement useful features.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The good news is that there are ways to gather feedback from others in the middle of implementing a feature that don&#x27;t slow you down! You can set up a 1-on-1 to talk through design decisions, exchange UML drawings, pair program, or just Slack your teammate a link to your branch. They can then respond at their own pace, without blocking you from doing your work.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="if-merge-requests-queue-up-addressing-concerns-becomes-nontrivial">If merge requests queue up, addressing concerns becomes nontrivial</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re impatient, you might not wait for one of your merge requests to be approved before starting work on the next. After all, they&#x27;re all part of one feature, so it makes sense to tackle them sequentially. So you start your <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code> branch before your <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code> branch is merged in.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In order to pull this off, however, you&#x27;ll need to branch <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code> <em>off of</em> <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code>, since the changes in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code> aren&#x27;t in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">main</code> yet. This works fine for now. And if your code is good, it&#x27;s not a problem, since you&#x27;ll just merge <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code> into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">main</code> first, then merge <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code> in after.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This falls apart as soon as your reviewer points out an issue with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code>. You switch to that branch and commit your fixes. Then, while waiting to hear back from your reviewer, you go back to developing on <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code>. But <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code> doesn&#x27;t have those changes, meaning you&#x27;ll have to either rebase it onto <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code> (which is annoying if any teammates also checked out that branch) or merge <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code>&#x27;s most recent commit into it (which clutters up history). Then your reviewer requests more changes, and you need to do the song and dance once again. You probably can&#x27;t just merge in all of your changes into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-2</code> once <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">feature-1</code> gets merged, since they&#x27;re so tightly coupled.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This gets quadratically worse if you have a longer chain of merge requests. I&#x27;ve seen situations like this up to four layers deep, where each change to the oldest MR require rebasing every other MR in sequence. This is an awful experience for everyone involved, and it leads to comments on merge requests like &quot;Fixed this in [later merge request], please approve this one now,&quot; which is definitely not the way things should work.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="reviewers-need-to-consider-a-certain-amount-of-context-regardless">Reviewers need to consider a certain amount of context regardless</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When you&#x27;re reviewing code changes, you aren&#x27;t just thinking about the code itself. You need to consider everything else in the system that the code interacts with.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Suppose you&#x27;re incrementally replacing a control stack for a device written in ROS. Conceptually, the new and old implementations will be quite different.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You could go about this by rewriting each node in turn, each as its own merge request. You&#x27;ll need to plan an order to migrate nodes in that allows you to reach the &quot;target&quot; system architecture, but you&#x27;ll also need to not break the existing system along the way, since the system should be functional at each point in between the merge requests. If you want to rename certain topics shared between nodes, or change the message type passed between nodes, you&#x27;ll need to carefully plan when you do that as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, your reviewers will need to consider each change in the context of both the existing system and the future/new system. Again, you can&#x27;t break things in between merge requests, but you also want to make sure you&#x27;re architecting things properly for the final system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Or, you can just replace the old system with a new system in a single, atomic merge request, allowing your reviewers to focus exclusively on reviewing the new system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For a different example: consider implementing a program that bridges a serial, I2C, or SPI connection and a Redis pub/sub channel, for instance forwarding messages from serial into Redis and vice versa. Suppose you want your program to handle several data types and have a consistent interface on each hardware interface.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You could break this feature into separate merge requests for each interface. Then, when your reviewers went to review <em>each</em> merge request, they would need to understand the management of the Redis connection, the datatype system used throughout the codebase, the differences in protocol between each of the planned hardware interfaces, and the handling of the specific hardware interface actually implemented in the MR.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This isn&#x27;t too bad if these merge requests are reviewed one after the other, but what if a week or more goes by between each of them? Your reviewer will need to remember each of these things, every time, ultimately leading to spending more time on review compared to just reviewing everything all at once.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="long-diffs-should-take-longer-to-review">Long diffs <em>should</em> take longer to review</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There&#x27;s a common refrain that &quot;you can&#x27;t possibly catch everything in a patch of more than X lines.&quot;</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Consider this: would you rather read one chapter of a book every week or two, or spend a few days reading as much as you can? Which would allow you to better follow the story? Keep track of the characters? Understand the book at a deeper level?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I see no fundamental reason why a reviewer would have a lower success rate at spotting issues per line of code for a larger diff as opposed to a smaller one. It will require more time to read and understand, and it will require a reviewer to potentially be more deliberate in their review, but it ultimately leads to the same quality assurance with substantially reduced effort for the development team.</p></div>]]></description>
            <link>https://breq.dev/2023/07/27/long-diffs</link>
            <guid isPermaLink="false">/2023/07/27/long-diffs</guid>
            <category><![CDATA[ci]]></category>
            <category><![CDATA[software]]></category>
            <pubDate>Thu, 27 Jul 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[So, You Want To Stream Lots Of USB Cameras At Once]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s say you&#x27;re building a live camera feed for something. Maybe it&#x27;s a 3D printer, or an RC car, or a <a href="https://www.northeasternrover.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Mars Rover analogue</a> (website hasn&#x27;t been updated in years). Here&#x27;s a pretty typical requirements list:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Video feed sent over an IP network</li>
<li class="my-2 pl-2">&lt;500ms latency</li>
<li class="my-2 pl-2">Reasonably efficient encoding (e.g. H.264)</li>
<li class="my-2 pl-2">640x480 resolution, 30 fps</li>
<li class="my-2 pl-2">Recover from network failures</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With one stream, you&#x27;re in pretty good shape! Just plug in any off-the-shelf USB camera, run a quick GStreamer command on both ends, and boom, you&#x27;re good to go. But as you add more cameras into the mix, you&#x27;ll run into some hiccups at seemingly every point in the pipeline.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This blog post is the culmination of 2 years of work into camera streaming pipelines on NU ROVER, Northeastern University&#x27;s team for the <a href="https://urc.marssociety.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">University Rover Challenge</a>. Over that time, we&#x27;ve evolved from an unreliable six-camera setup requiring four separate devices to an efficient, easy-to-use, reliable system encompassing 14 cameras on a single computer. This post will summarize the experimentation we did, the decisions we made, and the lessons we learned, starting with the individual camera modules themselves and working up through the pipeline all the way to how the feed is displayed.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cameras">Cameras</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A camera module connects some image sensor to some interface. The choice of image sensor doesn&#x27;t impact much; NU ROVER chooses camera modules based purely on FOV and resolution. The interface, however, greatly affects what hardware you can use for streaming.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="csi">CSI</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Camera Serial Interface (CSI), sometimes called &quot;MIPI,&quot; is the most barebones protocol for connecting a camera: the protocol is specifically designed for cameras and connected directly to the CPU on boards like the Raspberry Pi. A good example of this type is the Raspberry Pi Camera, but other manufacturers supply various cameras with different FOVs and sensors.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Pros:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Direct connection with processor, very low overhead</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Limited to number of CSI lanes provided by platform (e.g. 1 for Raspberry Pi, 2 for Jetson Nano, 6 for Jetson Orin)</li>
<li class="my-2 pl-2">Spec limits cable length to 30cm (although it usually works up to 2m)</li>
<li class="my-2 pl-2">Requires software tailored to the platform (e.g. <a href="https://www.raspberrypi.org/app/uploads/2013/07/RaspiCam-Documentation.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">raspivid</code></a>), which may complicate integration with streaming software</li>
<li class="my-2 pl-2">More fragile cables than USB</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the past, we used multiple devices (2 Raspberry Pis, a Jetson Nano, and a Jetson TX2) to connect many CSI cameras into our system. This dramatically increased complexity and required installation of a network switch, taking up precious space inside the rover. Our new system does not make use of CSI cameras.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="ip">IP</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">IP cameras are often used as part of home security systems, making them readily available. They directly connect using Ethernet, removing the need for a separate device for encoding. Our team purchased a few, and I spent some time testing them, but I ruled them out pretty quickly. They&#x27;re physically much harder to mount and integrate into a system, and since they&#x27;re typically designed for recording and not live viewing, they often have very high latency.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Pros:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Minimal configuration needed</li>
<li class="my-2 pl-2">Designed to scale up to &gt;10 camera systems</li>
<li class="my-2 pl-2">Durable and weatherproof</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Cameras are quite bulky and thus harder to find mounting points for</li>
<li class="my-2 pl-2">Cables are much larger and difficult to route</li>
<li class="my-2 pl-2">Requires using a (potentially very large) network switch to connect multiple</li>
<li class="my-2 pl-2">Provide limited or no encoder configuration (e.g. no way to adjust stream bitrate)</li>
<li class="my-2 pl-2">High latency (~1000-2000ms)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re working on a physically large, stationary, or spread-out system, IP cameras might be worth considering. Otherwise, the added bulk is a substantial drawback.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="analog-video">Analog Video</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This year, our team partnered up with NUAV, Northeastern&#x27;s unmanned aerial vehicles organization, to utilize a drone in part of the autonomous challenge. Their camera feed (used only during emergency teleoperation) was a 5.8 GHz off-the-shelf system common in FPV (first-person view) drone operation. These systems use an analog video signal to send video to the operator&#x27;s goggles. While analog video is tempting as a self-contained system, it doesn&#x27;t scale effectively to a multi-camera setup.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Pros:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Standalone system</li>
<li class="my-2 pl-2">Low latency</li>
<li class="my-2 pl-2">Graceful signal degradation if interference exists</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Requires a dedicated antenna, receiver, and display to view each feed</li>
<li class="my-2 pl-2">Each video feed requires its own hardware</li>
<li class="my-2 pl-2">Almost always fixed to 5.8 GHz (shorter range compared to 2.4 GHz WiFi or 900 MHz IP radios)</li>
<li class="my-2 pl-2">Limited number of channels/cameras (8)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We considered installing an analog video camera to use if our primary network link dropped out, but given the characteristics of the 5.8 GHz band, there are few situations in which this would be helpful. (If we can get a 5.8 GHz signal to the rover, we can almost definitely get a 2.4 GHz signal to it as well.)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usb">USB</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This brings us to USB cameras, arguably the most common type in consumer use today. These seem like an obvious choice, but there are a few complications when you scale beyond one or two cameras (which we&#x27;ll get into).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Pros:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Cable and connector are durable but not too bulky</li>
<li class="my-2 pl-2">Standard software support across platforms</li>
<li class="my-2 pl-2">Small modules available with standardized mounting holes from Arducam, ELP, etc.</li>
<li class="my-2 pl-2"><del>Unlimited number of cameras on each computer</del> (we&#x27;ll get to this...)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Some overhead compared with CSI</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s no surprise USB was our first choice when we began developing the system. Even if you rely on CSI cameras for some systems, you&#x27;re unlikely to be able to build a low-cost and practical system that incorporates 10 or more of them, meaning you&#x27;d have to mix in some USB cameras as well.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usb-bandwidth">USB Bandwidth</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;re all used to just plugging in USB devices arbitrarily and having everything work. Cameras, however, tend to push the USB 2.0 connection to its limit, and using several of them on one computer can be surprisingly difficult.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="a-bit-of-bandwidth-math">A bit of bandwidth math</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Consider the bandwidth requirement of a typical video stream: 640x480 pixels, 30 fps.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Quick note: If you&#x27;ve worked with images, you might assume we need 8 bits per channel to represent each pixel in the frame. This isn&#x27;t quite true -- we can save a little extra space with some smart encoding. Most raw video from USB cameras is in &quot;YUYV&quot; format, and contains three values: Y (luminance, or &quot;brightness&quot;), U (color), and V (also color). The eye is more sensitive to small variations in luminance than to similar variations in color, so we can save space by using 8 bits for luminance and 4 bits for each of the color signals, giving a total of 16 bits per pixel per frame.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The requirement for a single frame is thus:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>640</mn><mo>×</mo><mn>480</mn><mo>×</mo><mn>16</mn><mo>=</mo><mn>4915200</mn><mtext> bits</mtext></mrow><annotation encoding="application/x-tex">640 \times 480 \times 16 = 4915200 \text{ bits}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And the requirement for a single second of video is:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>4915200</mn><mo>×</mo><mn>30</mn><mo>=</mo><mn>147456000</mn><mtext> bit/s</mtext><mo>≈</mo><mn>147</mn><mtext> Mbit/s</mtext><mo>≈</mo><mn>18.4</mn><mtext> MByte/s</mtext></mrow><annotation encoding="application/x-tex">4915200 \times 30 = 147456000 \text{ bit/s} \approx 147 \text{ Mbit/s} \approx 18.4 \text{ MByte/s}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The theoretical maximum speed of USB 2.0 is 480 Mbit/s, and subtracting <a href="https://microchipdeveloper.com/usb:high-speed" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">signal overhead</a>, we&#x27;re only left with <strong>53.2 MByte/s</strong>. For a single camera, this is fine! We&#x27;ve got plenty of bandwidth left over.</p>
<img class="mx-auto" src="/images/cameras/01-single.svg" alt="A diagram showing a camera connected to a USB controller, showing only using a small portion of the total link capacity" width="154" height="224"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What happens when we add multiple cameras? It depends. Let&#x27;s first consider the case that the cameras are on different USB <em>controllers</em> from each other -- we&#x27;ll go over what this means in a second. This is also fine.</p>
<img class="mx-auto" src="/images/cameras/02-parallel.svg" alt="Three parallel copies of the prior diagram" width="434" height="224"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now what if we try to put multiple cameras on a single USB hub? Here&#x27;s where we start to run into problems.</p>
<img class="mx-auto" src="/images/cameras/03-hub.svg" alt="A diagram showing three cameras connected to one USB controller through a hub, where the link between the hub and the controller is oversaturated" width="434" height="374"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In a setup like this, two of the cameras will work fine, but the third will not work. Specifically, you&#x27;ll be able to open all three device files simultaneously, but you&#x27;ll only receive video frames from two of them. The third camera will not raise an error, which can make troubleshooting annoying.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If this seems weird for a reasonable USB setup to fail like this, that&#x27;s because it is. Most devices don&#x27;t come anywhere near to reaching the USB bandwidth limit. And if they do, they&#x27;ll typically handle it gracefully: your SSD might copy files over a little bit slower, or your printer might take a bit longer to load the document you sent it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With a USB camera, though, there&#x27;s no good way to handle a lack of bandwidth: it doesn&#x27;t have the memory to delay frames and send them later, it can&#x27;t send a smaller-sized image since the software expects a specific resolution, and there&#x27;s no way for it to coordinate with other cameras to share the bandwidth by slowing down framerate. Thus, you get nothing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s take a step back and look at what this means in practice. A USB <em>controller</em> is the &quot;root&quot; of the tree of USB devices. It&#x27;s a chip that connects the USB bus to the CPU, either directly or via a protocol like PCIe. In practice, most computers only have one or two controllers, then use internal USB hubs to provide more physical USB ports.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For instance, a Raspberry Pi 3 has four physical USB ports but only a single internal USB controller, meaning all of the ports share bandwidth. As a result, you&#x27;re unlikely to get more than two USB cameras working in YUYV mode at this resolution simultaneously on the Pi.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usb-30">USB 3.0</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, most modern devices have USB 3.0 ports. Can those help us out? USB 3.0 has a theoretical maximum rate of <strong><a href="https://en.wikipedia.org/wiki/USB#USB_3.x" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">500 Mbyte/s</a></strong>, so we&#x27;re good, right?</p>
<img class="mx-auto" src="/images/cameras/04-usb3-incorrect.svg" alt="An incorrect diagram showing the oversaturated link &quot;upgraded&quot; to USB 3.0 by replacing the hub and controller with USB 3.0 variants" width="434" height="374"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, <strong>this is not how this works.</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">USB 2.0 uses a single differential pair of wires for signaling. USB 3.0 adds extra wires to the cable and connector, for a total of 3 differential pairs. The two additional pairs are used for &quot;SuperSpeed&quot; signaling (the fast data rates), but the original pair is still just a regular USB 2.0 connection. USB 3.0 traffic is handled completely separately from USB 2.0 traffic.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In essence, <em>a &quot;USB 3.0 cable&quot; is a USB 3.0 cable and a USB 2.0 cable in the same housing</em>. Consequently, <em>a &quot;USB 3.0 hub&quot; is a USB 3.0 hub and a USB 2.0 hub in the same box, wired up to different pins in the same connectors</em>. And you guessed it, <em>a &quot;USB 3.0 controller&quot; is just a USB 3.0 controller and a USB 2.0 controller in the same chip</em>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s the output of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">lsusb -t</code> on a Raspberry Pi 4:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M</span></div><div class="" style="color:#404040"><span class="">/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M</span></div><div class="" style="color:#404040"><span class="">    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can see that despite the Pi 4 having a single USB controller chip, it appears in Linux as two entirely separate USB buses -- one for USB 2.0 (the &quot;480M&quot; is the speed of the port in bits) and the other for USB 3.0 (listed as &quot;5000M&quot;).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a diagram that more accurately captures what&#x27;s going on:</p>
<img class="mx-auto" src="/images/cameras/05-usb3-correct.svg" alt="the diagram showing the oversaturated link, but with a separate USB 3.0 link off to the side with zero utilization" width="444" height="384"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since the cameras are all USB 2.0, all of the data they send stays on the USB 2.0 pair all the way to the controller. (USB 3.0 cameras exist, but are expensive and rare outside of industrial settings.)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="mjpg-encoding">MJPG encoding</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Your webcam probably runs at much higher than 640x480p. The popular Logitech C920, for instance, supports 1080p video at 30 fps and runs over USB 2.0, which seems like it&#x27;d use up more bandwidth than we have, right? Something doesn&#x27;t add up.</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>1920</mn><mtext> pixels</mtext><mo>×</mo><mn>1080</mn><mtext> pixels</mtext><mo>×</mo><mn>16</mn><mtext> bits</mtext><mo>×</mo><mn>30</mn><mtext> fps</mtext><mo>≈</mo><mn>995.3</mn><mtext> Mbit/s</mtext><mo>≈</mo><mn>124.4</mn><mtext> Mbyte/s</mtext><mo>&gt;</mo><mn>53.2</mn><mtext> Mbyte/s</mtext></mrow><annotation encoding="application/x-tex">1920 \text{ pixels} \times 1080 \text{ pixels} \times 16 \text{ bits} \times 30 \text{ fps} \approx 995.3 \text{ Mbit/s} \approx 124.4 \text{ Mbyte/s} \gt 53.2 \text{ Mbyte/s}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These cameras support higher resolutions and framerates by compressing frames before sending them to the computer, using a technique known as MJPG (motion JPEG). Frames are captured by the camera, compressed using the JPEG algorithm, and then sent along the wire to the computer, at which point they can be decompressed.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Different cameras will use different compression ratios depending on the resolution and framerate, meaning we don&#x27;t have hard numbers to go off of. The improvement from motion JPEG in a multi-camera setup is unfortunately not that great: compression ratios are set assuming a given camera is the only device on the bus, so the bandwidth reduction at smaller resolutions is much less significant. In practice, I&#x27;ve found that up to 3 cameras can coexist on a single bus at this resolution and framerate, meaning it&#x27;s a pretty minor improvement.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="your-toolbox">Your Toolbox</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here are the tools you have to try to solve the USB bandwidth problem:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><strong>Add more devices, and split up the cameras between them.</strong> NU ROVER did this initially, but it complicates your entire system quite a bit -- you now have to deal with a network switch, plus however many extra devices you brought in.</li>
<li class="my-2 pl-2"><strong>Find a way to add more USB controllers to a particular device.</strong> When we upgraded our systems to the Jetson Orin, we added a USB expansion card into the PCIe slot. In particular, we chose a <a href="https://www.microcenter.com/product/439934/vantec-quad-chip-4-port-dedicated-5gbps-usb-30-pcie-host-card" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;Quad Chip&quot; model</a> which contained four separate USB controllers. These are popular among VR enthusiasts -- it turns out connecting many stationary VR tracking towers leads to similar bandwidth issues as USB cameras.</li>
<li class="my-2 pl-2"><strong>Switch to the MJPG mode instead of the YUYV one.</strong> This adds a bit of latency since the device now has to decode the MJPG image, but the bandwidth improvements are often significant enough to make it worthwhile. Plus, the MJPG modes are typically more similar across camera models -- some cameras can be picky about what framerates they accept for YUYV streaming, for instance.</li>
<li class="my-2 pl-2"><strong>Plan which camera connects to which controller carefully.</strong> If you plan on dynamically starting and stopping streams, and you know you&#x27;ll never want two specific cameras running simultaneously, you can put them on the same controller and they&#x27;ll never have to fight over bandwidth. Conversely, if there&#x27;s a set of cameras you plan on using all together, make sure not too many of them are on the same controller.</li>
<li class="my-2 pl-2"><strong>Apply some driver tweaks to squeeze out a little extra bandwidth.</strong> The Linux UVC driver provides a mode (<a href="https://stackoverflow.com/a/25619508" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">UVC_QUIRKS_FIX_BANDWIDTH</code></a>) that calculates the bandwidth requirements for each device itself, which can sometimes be more accurate than what the camera reports.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="encoding">Encoding</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You&#x27;ve gotten an image from the camera. Now what?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You&#x27;ll probably want to use <a href="https://gstreamer.freedesktop.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GStreamer</a> to link together most of your pipeline. GStreamer is a framework that allows you to chain together different video elements, such as sources, encoders, payloaders, and decoders, into a pipeline that is executed all at once. The concepts in this post aren&#x27;t GStreamer-exclusive, and I won&#x27;t be providing many &quot;ready-to-go&quot; pipelines since they vary based on hardware encoding support and system, but I will at times assume that you&#x27;re linking together your source, encoder, and payloader using GStreamer.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The most common codec for video compression is H.264, which is generally good enough for most purposes. Other codecs like H.265, AV1, and VP9 can offer lower bandwidth, but the benefits are minor at smaller video resolutions. The most important factor is hardware encoding and decoding support; if the encoding is handled by the CPU, it may not keep up with many video streams simultaneously, especially on lower-end devices.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The most important factors to tune are bandwidth and keyframe interval. <strong>Bandwidth</strong>, in this case, is the amount of video data sent over the network per second. If your bandwidth is set too high, you&#x27;ll start to see artifacts as packets are dropped whenever your network link slows down (for instance, if you have a wireless link and your robot drives behind a wall). If it&#x27;s set too low, your video quality will suffer and your feed will be full of compression artifacts. If you&#x27;re operating over a wired Ethernet connection, you can probably divide the bandwidth of the Ethernet link by however many streams you plan to have simultaneously, but take into account any other network traffic on the machine, too. NU ROVER uses a bandwidth of 1 Mbps for each 480p stream.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Keyframe interval</strong>, also known as &quot;I-frame interval,&quot; refers to the time in between I-frames in the video signal. H.264 encoded video consists of I-frames (frames which contain a complete image), P-frames (which reference previous frames), and B-frames (which can reference future frames and are only relevant in the context of a pre-recorded video). The purpose of P-frames is to improve the video compression by not sending the same data in every frame -- if your camera feed is mostly a still image, it is wasteful to send that entire image on every frame when you can instead only send the specific areas of the image which changed.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you have too many I-frames, you will devote too much of your bandwidth to them and have worse picture quality as a result. However, if you have too few, you may have periods where you can&#x27;t see the entire image if an I-frame gets dropped due to network conditions. This usually manifests itself as a gray screen in which moving areas gradually return to color while motionless areas remain gray until the next I-frame. NU ROVER uses 1 keyframe every 30 seconds.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="protocols">Protocols</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now that you have your encoded video feed, you&#x27;ll need to find a way to send it over the network. There are a few existing protocols that can help you here. Of course, you&#x27;ll need to use the same protocol on both sides of the connection.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motion-jpeg">Motion JPEG</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://en.wikipedia.org/wiki/Motion_JPEG" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Motion JPEG</a>, also known as MJPG, also works for sending video over a network, frame by frame, usually over HTTP. Each frame is compressed individually, making it one of the simplest protocols. However, because there&#x27;s no inter-frame compression, this protocol uses much more bandwidth to stream video at a given quality than others.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="http-live-streaming">HTTP Live Streaming</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">HTTP Live Streaming</a> (HLS) is a protocol used to stream H.264 videos over the internet. It works by dividing the video into chunks, then serving each chunk in turn using HTTP. This chunking process introduces a high amount of latency, making HLS unsuitable for a low-latency feed.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="real-time-transport-protocol">Real-time Transport Protocol</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Real-time Transport Protocol (RTP) is one of the simplest ways to send video over a network. It sends data over UDP, meaning network errors and dropped packets are not corrected like they would be with a TCP-based protocol. This is ideal for low-latency applications -- if one frame isn&#x27;t received correctly, you don&#x27;t want to spend time retransmitting it when you could instead be transmitting the next frame. However, RTP could be a poor choice for streams where quality is more important than latency.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">RTP works by payloading data into packets and sending them to a predefined UDP host and port. This payloading process will look different depending on the codec you choose -- for now, let&#x27;s assume we&#x27;re using H.264. GStreamer provides the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtph264pay</code> plugin to payload H.264 data, as well as the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtph264depay</code> plugin to depayload it on the other end.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For example, let&#x27;s assume the video server is on <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">192.168.1.10</code>, the client is on <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">192.168.1.11</code>, and the video stream should be on port <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">9090</code>. We can set up an RTP stream by running this GStreamer pipeline on the server:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">gst-launch-1.0 videotestsrc </span><span class="" style="color:#8B5CF6">!</span><span class=""> video/x-raw,rate</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">30</span><span class="">,width</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">640</span><span class="">,height</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">480</span><span class=""> </span><span class="" style="color:#8B5CF6">!</span><span class=""> x264enc </span><span class="" style="color:#ff218c">tune</span><span class="" style="color:#8B5CF6">=</span><span class="">zerolatency </span><span class="" style="color:#8B5CF6">!</span><span class=""> rtph264pay </span><span class="" style="color:#8B5CF6">!</span><span class=""> udpsink </span><span class="" style="color:#ff218c">host</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">192.168</span><span class="">.1.11 </span><span class="" style="color:#ff218c">port</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">9090</span></div></pre></div>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">videotestsrc</code> is just a generic test video source (color bars)</li>
<li class="my-2 pl-2">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">video/x-raw</code> part forces the output of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">videotestsrc</code> to have the specified resolution and framerate</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">x264enc</code> is the encoder (which runs on the CPU -- something to avoid)</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtph264pay</code> payloads the stream for RTP</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">udpsink</code> sends those payloaded packets over the network to the specified host and port (our client)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, on our client, we can run this to decode the stream:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">gst-launch-1.0 udpsrc </span><span class="" style="color:#ff218c">port</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">9090</span><span class=""> </span><span class="" style="color:#8B5CF6">!</span><span class=""> application/x-rtp,payload</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">96</span><span class=""> </span><span class="" style="color:#8B5CF6">!</span><span class=""> rtph264depay </span><span class="" style="color:#8B5CF6">!</span><span class=""> avdec_h264 </span><span class="" style="color:#8B5CF6">!</span><span class=""> autovideosink</span></div></pre></div>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">udpsrc</code> reads incoming packets on a particular UDP port</li>
<li class="my-2 pl-2">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">application/x-rtp</code> part says that those packets should be interpreted as an RTP stream</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtph264depay</code> turns the packets into a raw H.264 stream</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">avdec_h264</code> decodes the H.264 stream (on the CPU)</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">autovideosink</code> displays the stream</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To scale this to multiple camera feeds, we just need to choose different UDP ports for each feed. There are plenty of available port numbers for this.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="real-time-streaming-protocol">Real Time Streaming Protocol</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The solution above using RTP leaves a few things to be desired:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Having to manually assign port numbers is annoying</li>
<li class="my-2 pl-2">Starting or stopping streams must be done by manually launching or killing processes on the server</li>
<li class="my-2 pl-2">RTP (in mode 96) doesn&#x27;t work with VLC or other standard media players</li>
<li class="my-2 pl-2">The server must know the IP address of the client, not the other way around</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Real Time Streaming Protocol</a>, or RTSP, is a protocol built on top of RTP which can solve these shortcomings. RTSP at first behaves much like an HTTP server, and RTSP URLs look similar to HTTP URLs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s look at what happens when a client tries to load <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtsp://192.168.1.10/video</code>:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The client opens a connection to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">192.168.1.10</code> on port <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">554</code> (the default RTSP port)</li>
<li class="my-2 pl-2">The client sends an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">OPTIONS</code> request to the server for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/video</code>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The server responds with a list of request types accepted (usually <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">OPTIONS</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">DESCRIBE</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PLAY</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">TEARDOWN</code>...)</li>
</ul></div>
</li>
<li class="my-2 pl-2">The client sends a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">DESCRIBE</code> request for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/video</code>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The server responds with a list of available video formats</li>
</ul></div>
</li>
<li class="my-2 pl-2">The client sends a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SETUP</code> request for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/video</code>, and provides a pair of open ports: an even-numbered port for receiving video data, and an odd-numbered port for receiving UDP control signals using RTCP (RTP Control Protocol).
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The server responds with its own pair of ports</li>
</ul></div>
</li>
<li class="my-2 pl-2">The client sends a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PLAY</code> request for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/video</code>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The server begins sending video data to the client on the specified ports</li>
</ul></div>
</li>
<li class="my-2 pl-2">The client sends a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">TEARDOWN</code> request for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/video</code>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The server stops sending video data to the client</li>
</ul></div>
</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">RTSP provides a few other methods, including <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PAUSE</code>, which can be useful for interruptable streams.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To play around with RTSP, you can use the <a href="https://github.com/sfalexrog/gst-rtsp-launch" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">gst-rtsp-launch</code></a> project:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">gst-rtsp-launch </span><span class="" style="color:#1bb3ff">&quot;( videotestsrc ! video/x-raw,rate=30,width=640,height=480 ! x264enc tune=zerolatency ! rtph264pay pt=96 name=pay0 )&quot;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, connect with GStreamer&#x27;s <a href="https://gstreamer.freedesktop.org/documentation/rtsp/rtspsrc.html?gi-language=c" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtspsrc</code></a> plugin:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">gst-launch-1.0 rtspsrc </span><span class="" style="color:#ff218c">location</span><span class="" style="color:#8B5CF6">=</span><span class="">rtsp://127.0.0.1:8554/video </span><span class="" style="color:#ff218c">latency</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">0</span><span class=""> </span><span class="" style="color:#8B5CF6">!</span><span class=""> rtph264depay </span><span class="" style="color:#8B5CF6">!</span><span class=""> avdec_h264 </span><span class="" style="color:#8B5CF6">!</span><span class=""> autovideosink</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Or, connect with VLC by entering the RTSP URL into the &quot;Open Network Stream&quot; window.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re building out a system with multiple video streams, you&#x27;ll want to use the GStreamer bindings to define your own RTSP server&#x27;s request handling logic. <a href="https://github.com/tamaggo/gstreamer-examples/blob/master/test_gst_rtsp_server.py" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Here&#x27;s an example</a> of how to use the Python bindings, but bindings for C++ and other languages also exist. NU ROVER has our own in-house implementation which we may decide to open-source once it stabilizes, assuming we have time to do so.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="extra-signaling-logic">Extra Signaling Logic</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One limitation of RTSP is that by default there is no way for a client to list the streams available on the server. If you don&#x27;t want to maintain a list of streams on both sides of the connection, you may want to add some extra logic to handle this.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One approach would be to try to send this data over the RTSP connection somehow, say, by returning it in response to a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">DESCRIBE</code> request for a certain URL. Doing this would require the ability to embed this handling logic into whatever RTSP client and server you use, which could be difficult.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The approach NU ROVER took instead was to use another communication channel which we already had (<a href="https://www.ros.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ROS</a> messages) to send this data. However, you could rely on an HTTP API, a database, or some other method. You might want such a protocol to handle:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Listing available stream paths on the server</li>
<li class="my-2 pl-2">Sending metadata for browsing (stream name, description, etc)</li>
<li class="my-2 pl-2">Sending technical metadata (resolution, framerate, camera orientation, bandwidth requirements, etc)</li>
<li class="my-2 pl-2">Handling &quot;exclusivity&quot; of streams (e.g. if two streams are from the same camera but at different resolutions, and only one can be used at a time)</li>
<li class="my-2 pl-2">Sending calibration data and exact positions for relevant cameras, to enable computer vision uses on the other end of the RTSP stream</li>
<li class="my-2 pl-2">Enabling or disabling baked-in overlays in the video signals (timestamp, camera name, etc)</li>
<li class="my-2 pl-2">Pausing the video stream to take high-resolution &quot;snapshots&quot; of the video feed</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="clients">Clients</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What client are you going to use to decode and display the video signal? You&#x27;ll likely want your video feeds to be integrated into whatever control UI you use for other operations, so this will depend on your situation.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="vlc">VLC</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://www.videolan.org/vlc/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">VLC Media Player</a> is a common program for playing various media files, and it can play video from an RTSP source. Go to &quot;File&quot; → &quot;Open Network Stream&quot; and enter your RTSP URL.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">VLC is typically meant for watching movies or other prerecorded content, meaning it buffers video to smooth out playback, dramatically increasing latency. While there are ways to reduce the latency (I couldn&#x27;t find a good comprehensive source, so just search for &quot;VLC RTSP low latency&quot;), I haven&#x27;t had much success. I&#x27;d suggest keeping VLC around to help with troubleshooting streams, but not to use it in your &quot;production&quot; system.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="gstreamer">GStreamer</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">GStreamer&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtspsrc</code> can also load video data over RTSP, and its <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">glimagesink</code> plugin can display video data in a window, making it functional as a standalone RTSP viewer. (Remember to specify <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">latency=0</code> for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rtspsrc</code>.) However, if you want your video feed to be displayed in the same window as the rest of your UI, you&#x27;ll likely need something more complex.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="qt-etc">Qt, etc.</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bindings often exist to display media from a GStreamer pipeline in applications built with frameworks like Qt (<a href="https://gstreamer.freedesktop.org/modules/qt-gstreamer.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">QtGStreamer</a>), React Native (<a href="https://github.com/Kalyzee/react-native-gstreamer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">react-native-gstreamer</code></a>), and others. You can find a pipeline that works by launching with the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">gst-launch-1.0</code> command, then copy it into your application.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="browsers">Browsers</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Web-based user interfaces are becoming much more popular, and NU ROVER has our own based on React. Of course, you can&#x27;t run arbitrary C++ code inside of a browser sandbox, nor can you access UDP/TCP ports directly, so embedding a GStreamer widget directly in the browser isn&#x27;t feasible.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="browser-specifics">Browser Specifics</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Sending low-latency video to a web browser is more difficult than it would seem. Browsers are built for media consumption, meaning they often buffer video to smooth out playback at the cost of higher latency. There are quite a few ways to include videos in a web browser, none optimal.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="http-live-streaming-1">HTTP Live Streaming</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">HLS seems like it&#x27;s exactly right for this use case, but the required &quot;chunking&quot; of video data adds a high amount of latency, making it unusable for a low-latency video feed.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="raw-h264">Raw H.264</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is possible to directly send an H.264 stream to the browser. However, pre-existing solutions for <em>streaming</em> H.264 are difficult to come by. One example is the ROS <a href="https://github.com/RobotWebTools/web_video_server" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">web_video_server</code></a> package, which implements its own HTTP server to handle this.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The benefit of this approach is that video can be embedded in a website with a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;video&gt;</code> tag, and the video is compressed when entering the browser, reducing bandwidth use and allowing the browser to choose the most efficient decoder for the platform. The important drawback is that <em>there is no way to control the amount of buffering that the browser does to the video feed</em>, and browsers like to buffer a significant chunk of frames to ensure smooth playback. NU ROVER experimented with using JavaScript to automatically seek the video to the most recent frame in the buffer, but found this difficult and unreliable.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motion-jpeg-1">Motion JPEG</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One potential approach is to run a GStreamer pipeline on the client which decodes the H.264 data, then compresses each frame as a JPEG, then sends those into the browser.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Sending a series of images to the browser can be done using the <a href="https://en.wikipedia.org/wiki/Motion_JPEG#Video_streaming" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">multipart/x-mixed-replace</code> mimetype</a>: the server will send an HTTP response with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Content-Type: multipart/x-mixed-replace;boundary=--&lt;boundary_name&gt;</code>, then send a series of MJPG frames, separated by <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--&lt;boundary_name&gt;</code> lines.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is the approach NU ROVER currently uses. We&#x27;ve found that encoding and decoding JPEG images adds a small but acceptable amount of latency. We use an in-house project to serve these images, starting and stopping the underlying GStreamer pipelines and RTSP streams on demand.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A tip for using GStreamer with an application like this: to send buffers from a GStreamer pipeline into your application, you&#x27;ll want to use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">appsink</code>. Here&#x27;s an <a href="https://github.com/jackersson/gst-python-tutorials/blob/master/launch_pipeline/run_appsink.py" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">example</a> of code that uses this.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motion-bmp">Motion BMP?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In theory, you could send a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">multipart/x-mixed-replace</code> payload with any image format, not just JPEG. Sending an uncompressed bitmap image (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.bmp</code>), for instance, would remove the need to encode and decode the JPEG but would increase the data sent to the browser. In our case, we&#x27;re running the client on the same machine as the browser, meaning this added data transfer is largely irrelevant.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This approach is a bit harder to implement than Motion JPEG: effectively, you just need to add the proper headers to make each frame a BMP file, but we haven&#x27;t found a good GStreamer plugin that does this. In theory, this could be done in the HTTP server when the GStreamer buffers are read.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="stream-limit">Stream Limit</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you try to run more than six concurrent Motion JPEG streams into a browser, you&#x27;ll notice that beyond the first six, none of them load. To prevent creating too many HTTP connections when a site loads, browsers will restrict the number of concurrent HTTP connections to a single host to six.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In Firefox, it&#x27;s possible to change this setting by going to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">about:config</code> and changing the <a href="https://kb.mozillazine.org/Network.http.max-persistent-connections-per-server" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">network.http.max-persistent-connections-per-server</code></a> setting from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">6</code> to a higher value, say, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">20</code>. However, there is no way to change this setting in Chrome.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One workaround, if you have a DNS server handy or can modify <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code>, is to configure multiple subdomains for the same server, as described in this <a href="https://stackoverflow.com/a/23887902" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">StackOverflow answer</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An example <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code> file could look like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">cameras1.local 192.168.1.10</span></div><div class="" style="color:#404040"><span class="">cameras2.local 192.168.1.10</span></div><div class="" style="color:#404040"><span class="">cameras3.local 192.168.1.10</span></div><div class="" style="color:#404040"><span class="">cameras4.local 192.168.1.10</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Alternatively, a DNS server could be used with a <a href="https://en.wikipedia.org/wiki/Wildcard_DNS_record" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">wildcard record</a> to allow any subdomain to work.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, ensure that no more than six streams are loaded using the same subdomain, and all streams should work fine.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="webrtc">WebRTC</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One potential approach is using <a href="https://web.dev/webrtc-basics/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">WebRTC</a>, which is built for low-latency applications like video conferencing. We haven&#x27;t investigated this approach yet for a few reasons. WebRTC is primarily aimed at browser-based applications exchanging data with each other, not receiving data from a non-browser application, so example code for non-browser environments streaming into browsers is hard to find. WebRTC is also built around the assumption that it needs to traverse NAT using a <a href="https://en.wikipedia.org/wiki/STUN" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">STUN</a> server -- not only is that not necessary for us, it&#x27;s impossible as our network runs completely offline. That said, the use of WebRTC for low-latency streaming holds potential. I&#x27;d love to see this investigated, and hope I&#x27;ll get the time to play around with it myself at some point.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ve walked through the steps in building a camera streaming pipeline, from the choice of individual camera modules to the framework used to display them to the user. This guide is the resource I wish our team had when developing our streaming pipeline, and covers many of the pitfalls and decisions that will need to be made. It&#x27;s far from complete; there are many techniques and topics in streaming to explore that simply fall outside of what we&#x27;ve worked with, but I hope it&#x27;s useful in whatever system you&#x27;re building nonetheless.</p></div>]]></description>
            <link>https://breq.dev/2023/06/21/cameras</link>
            <guid isPermaLink="false">/2023/06/21/cameras</guid>
            <category><![CDATA[robotics]]></category>
            <category><![CDATA[cameras]]></category>
            <category><![CDATA[gstreamer]]></category>
            <category><![CDATA[web]]></category>
            <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[LDSP: low-level audio programming with rooted Android phones]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">A few weeks ago, I achieved one of my dreams: being on a team that publishes a paper from doing something cool. Here it is in full (shoutout <a href="https://www.nime.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NIME</a> for being open-access):</p>
<iframe src="https://drive.google.com/file/d/1UVR7LiwlrKoff6cJI32-H6zWrwyu4-4R/preview" class="w-full mb-2" height="500" allow="autoplay"></iframe>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">You can see my cameo as a hand model in Figure 5.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="our-vision">Our Vision</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The project was pitched to me by my advisor roughly like this:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Boards like the Raspberry Pi, <a href="https://bela.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Bela</a>, etc. are excellent tools that enable people to learn audio programming and make creative instruments using sensor data.</li>
<li class="my-2 pl-2">These boards can be expensive, and are especially difficult to find in certain parts of the world like Latin America.</li>
<li class="my-2 pl-2">A commodity smartphone, even an older one, has plenty of sensors, processing power, and audio capabilities to support this use case.</li>
<li class="my-2 pl-2">Old smartphones, specifically Android ones, are quite readily available in most parts of the world.</li>
<li class="my-2 pl-2">We just need to find a way to get a software environment low-latency audio I/O on these devices and package it into something easy to use.</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="my-work">My Work</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our approach was to circumvent the JVM entirely by rooting the phones, building binaries for them on a host computer, and executing these binaries directly. This gave us more direct access to the hardware, at the cost of a more difficult setup process. I focused on two main goals: our sensor I/O and our build system.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sensor-io">Sensor I/O</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This work involved working with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">libandroid</code>, Android&#x27;s internal library used for handling device functions. I spent a ton of time in Android Code Search to investigate how everything fits together, and started to put together some test functions for adjusting sensor sampling rates and reading data, which my advisor then turned into LDSP&#x27;s user-facing sensor API, providing functions like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">sensorRead</code> -- taking heavy inspiration from the Arduino IDE&#x27;s simple functions like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">analogRead</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="build-system">Build System</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our build system draws heavily on the <a href="https://developer.android.com/ndk" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Android NDK</a>, or Native Development Kit. This toolchain is designed to build smaller native components within a larger JVM-based app, so we needed to circumvent the intended way of doing things a bit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Initially, we just used manually invoked the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">g++</code> binary included with the NDK to compile our executables. However, as the project grew and we began pulling in more and more libraries, it became clear another solution was needed. We decided to start with Makefiles, due to their popularity and simplicity. This worked for a while, and even let us include some other scripts in the file (e.g. we set up <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">make push</code> to push the binaries to the phone).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This solution worked well as we continued development, but started to stretch when we wanted to make our build system more user-friendly. We wanted to provide a configuration file for each phone we supported, so users would not need to configure compiler settings for their phone manually. Many phone model names contain spaces. Furthermore, we wanted to let users specify a path to their project, and compile some of their files alongside our files. Allowing user project names to contain spaces was a hard requirement for us.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As it turns out, Makefiles handle spaces extremely poorly -- not only do they have to be escaped differently in different places, but many functions treat a variable with spaces implicitly as a list, causing issues. Many <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">make</code> builtins simply could not handle spaces in filenames, so we were forced to use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">$(shell ...)</code> and rely on shell commands (which aren&#x27;t portable across operating systems). Our solution was still plagued with bugs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Eventually, we gave up on pure Makefiles and I started to investigate other build systems. Ninja seemed promising, and has the backing of projects like Chromium and LLVM. We rolled out some CMake files across the repo, and set the output format to Ninja files, and... success!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, CMake isn&#x27;t necessarily built for running random commands in the way pure Makefiles are, so I created a few wrapper scripts: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ldsp.sh</code> for Linux and macOS and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ldsp.bat</code> for Windows. Each script allows configuring the project, building (with incremental builds), pushing the build to the phone, and running the build.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We got to see everything come together in an all-day workshop we did with many students from NU Sound. While the install process was a bit convoluted, most participants were able to get a toolchain up and running on their computer!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="future-goals">Future Goals</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The biggest hurdle with our current setup is complexity: participants need the Android NDK, CMake, Ninja, ADB (Android Debug Bridge), and more installed on their computer. If we could package everything into an easy-to-install application, we could make this project more accessible to those unfamiliar with the command line or programming in general, and make it much more fitting as an educational tool. Furthermore, if we could install this package onto the phone itself, we could remove the need for a host device altogether, making our work accessible to those without a laptop or desktop.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve been working on cross-compiling LLVM to run natively on an Android host. Admittedly, I haven&#x27;t had much luck so far, but it&#x27;s still early days. Once we have this crucial portion working, we can pivot to developing a web UI which runs on the phone to support editing and running code from any connected device, then package everything up into an .apk for people to install.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="thanks">Thanks</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I want to thank my advisor, <a href="https://toomuchidle.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Victor Zappi</a>, for giving me the opportunity to work on this amazing project.</p></div>]]></description>
            <link>https://breq.dev/2023/06/17/ldsp</link>
            <guid isPermaLink="false">/2023/06/17/ldsp</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[audio]]></category>
            <pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[How To Reverse an Android App]]></title>
            <description><![CDATA[<div class="e-content font-body"><blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This blog post follows my journey to learn about reverse-engineering on Android over a few months. Unlike a traditional project writeup, the structure of this piece matches the process of discovery I took. Dead-ends, useless tangents, and inefficient solutions have been intentionally left in.</p>
</blockquote>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-idea">The Idea</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I live in Boston currently, which has a wonderfully extensive <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="mbta">transit network</a>. To navigate between unfamiliar areas, I use an app called <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="transitapp">Transit</a>. Transit (or TransitApp, as I tend to call it) tracks bus, train, and subway departures in realtime to provide directions and time predictions.</p>
<div class="contents break-inside-avoid print:block"><img alt="A screenshot of the app&#x27;s interface, showing a map and a list of transit lines." loading="lazy" width="1080" height="2400" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ftransitapp%2Fscreenshot.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Ftransitapp%2Fscreenshot.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ftransitapp%2Fscreenshot.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">TransitApp also has a &quot;gamification&quot; system, in which you can set an avatar, report the location of trains from your phone, and show up on leaderboards.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Instead of public-facing usernames or profile pictures, TransitApp&#x27;s social features work off of random emoji and generated names. You are given the option to generate a new emoji/name combination.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For reasons I won&#x27;t get into, I really wanted the 🎈 emoji as my profile. It&#x27;s not on the list of emoji that the shuffling goes through (I shuffled for quite a while to confirm). If you pay for &quot;Transit Royale&quot; (the paid tier), you can choose your emoji yourself. However, I was bored, so I decided to see if I could find a more fun way to get it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My hypothesis was that the app is doing this &quot;shuffling&quot; logic on the client side, then sending a POST request to the server with the emoji to be chosen. If true, this would mean that I could replay that request, but with an emoji of my choosing.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="methods">Methods</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="android-emulator">Android Emulator</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because messing with a physical Android device seemed tricky, I decided to try using the Android emulator built into Android Studio. (No need to create a project: just click the three dots in the upper right and choose &quot;Virtual Device Manager.&quot;) After picking a configuration that supported the Google Play Store (I chose the Pixel 4), I installed TransitApp and logged in without issue.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="burp-suite">Burp Suite</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="burpsuite">Burp Suite</a> is the standard tool for inspecting and replaying HTTP network traffic. Burp Suite creates an HTTP proxy, and is able to inspect traffic via this proxy.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I began by setting Burp to bind on all interfaces, then set the proxy in the emulated phone&#x27;s settings to point at my computer&#x27;s IP address and proxy port.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Burp Suite&#x27;s analysis tools effectively break HTTPS, since it&#x27;s the literal definition of a man-in-the-middle attack. In other words, TLS protects the connection from my phone to any cloud services, and for Burp to mess with that, it needs a way to circumvent TLS. One approach is to install Burp&#x27;s certificates as a &quot;trusted&quot; certificate, effectively making Burp Suite able to impersonate any website it wants.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most guides for manually installing CA certificates on Android require a rooted operating system, but with a reasonably recent OS, it&#x27;s possible to install them in the phone&#x27;s settings, by going to: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Settings</code> -&gt; <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Security &amp; privacy</code> -&gt; <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">More settings</code> -&gt; <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Encryption &amp; credentials</code> -&gt; <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Install a certificate</code> -&gt; <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CA certificate</code>. This adds a &quot;Network may be monitored&quot; warning to the phone&#x27;s Quick Settings page.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This works perfectly! Now let&#x27;s just fire up TransitApp, and... nothing. It looks like TransitApp doesn&#x27;t respect the user&#x27;s proxy settings.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few apps claim to be able to use a VPN profile to force all traffic over a proxy, but this seems to not work properly for any I tested.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wireshark">Wireshark</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="wireshark">Wireshark</a> is a much more general-purpose network traffic capture tool. It supports capturing network traffic and filtering, making it relatively easy to inspect traffic.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, Wireshark cannot circumvent TLS. As a result, even though the presence of traffic is visible, it cannot be actually inspected. When filtering for DNS traffic, though, we do get something helpful: the domain names that TransitApp uses. We see a few:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api-cdn.transitapp.com</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stats.transitapp.com</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">service-alerts.transitapp.com</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.revenuecat.com</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">bgtfs.transitapp.com</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.transitapp.com</code></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With the exception of RevenueCat, which seems to manage in-app subscriptions, all domains are <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">*.transitapp.com</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="burp-suite-invisible-mode">Burp Suite Invisible Mode</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Burp Suite&#x27;s <a href="https://portswigger.net/burp/documentation/desktop/tools/proxy/invisible" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Invisible Mode</a> allows the proxy to work for devices that aren&#x27;t aware of its existence, by relying on the host OS to override DNS queries for the relevant domain names and send them to the proxy instead. To get this to work, we can run the Burp <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.jar</code> with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">sudo</code>, then set up two proxies: one on port 80 for HTTP and one on port 443 for HTTPS. Make sure to enable &quot;invisible proxying&quot; for both.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since we&#x27;re using invisible proxying, we&#x27;ll need to explicitly tell Burp where to forward the traffic -- the HTTP(S) requests themselves won&#x27;t have enough information for Burp to route them onwards. We can make a DNS request to find the IP address of the serer hosting <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.transitapp.com</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ dig @1.1.1.1 api.transitapp.com</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; @1.1.1.1 api.transitapp.com</span></div><div class="" style="color:#404040"><span class="">; (1 server found)</span></div><div class="" style="color:#404040"><span class="">;; global options: +cmd</span></div><div class="" style="color:#404040"><span class="">;; Got answer:</span></div><div class="" style="color:#404040"><span class="">;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 38753</span></div><div class="" style="color:#404040"><span class="">;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">;; OPT PSEUDOSECTION:</span></div><div class="" style="color:#404040"><span class="">; EDNS: version: 0, flags:; udp: 1232</span></div><div class="" style="color:#404040"><span class="">;; QUESTION SECTION:</span></div><div class="" style="color:#404040"><span class="">;api.transitapp.com.		IN	A</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">;; ANSWER SECTION:</span></div><div class="" style="color:#404040"><span class="">api.transitapp.com.	248	IN	A	34.102.188.182</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">;; Query time: 55 msec</span></div><div class="" style="color:#404040"><span class="">;; SERVER: 1.1.1.1#53(1.1.1.1)</span></div><div class="" style="color:#404040"><span class="">;; WHEN: Fri Jun 16 18:27:14 EDT 2023</span></div><div class="" style="color:#404040"><span class="">;; MSG SIZE  rcvd: 63</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Configure both proxies to route traffic to that IP address, keeping the ports the same.</p>
<div class="contents break-inside-avoid print:block"><img alt="A screenshot of Burp Suite&#x27;s proxy settings showing the stated configuration" loading="lazy" width="2876" height="1724" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ftransitapp%2Fburp.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Ftransitapp%2Fburp.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;ve played with networking before, your first instinct will probably be to mess with the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code> file to override DNS for the domains you want to intercept. This is a pretty common technique for web-based attacks, so let&#x27;s give it a try. One hiccup: we can&#x27;t put wildcards directly in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code>, so we&#x27;ll have to list each one individually. Worse, we&#x27;ll actually have to only do one at a time, since Burp Suite can only forward traffic to one IP at a time. Start with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.transitapp.com</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Aaaand... it still doesn&#x27;t work. Android is using something called Private DNS to automatically route traffic over HTTPS, meaning it can&#x27;t be tampered with as easily as traditional DNS. But even if you turn that off in the emulated phone&#x27;s network settings, it still doesn&#x27;t work, because the emulator doesn&#x27;t respect the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code> file. You&#x27;ll need to run a DNS server.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">First, find the IP address of the emulated phone and your computer. In the phone, go to the network settings page, then look for &quot;IP Address&quot; and &quot;Gateway&quot;: those are the IPs of the phone and your computer, respectively.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Install dnsmasq on your host computer and run it (here&#x27;s a nice <a href="https://gist.github.com/ogrrd/5831371" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">guide</a> for macOS). Set the Android DNS settings to point to your computer&#x27;s IP. And then update your <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code> entry to point to the IP address of your computer on the network instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">127.0.0.1</code>, since otherwise the emulated phone would attempt to connect to <em>itself</em>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Even still, this doesn&#x27;t work. If you try to inspect traffic, relevant features of the app will simply stop working and you will not see any connection trying to be made.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="static-analysis">Static Analysis</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Maybe some static analysis could help? We can download an APK from a <a href="https://m.apkpure.com/transit-bus-subway-times/com.thetransitapp.droid/download" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">definitely legitimate source</a>, then unzip it (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.apk</code> files are just <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.zip</code> files).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first thing we can do is to start looking for URLs. We already know that some URLs start with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">api.transitapp.com</code>, so let&#x27;s try looking for that:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg </span><span class="" style="color:#1bb3ff">&quot;api.transitapp.com&quot;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">No results. What about just <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&quot;transitapp.com&quot;</code>? Still nothing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It could be substituted in somewhere. Maybe we could look for the beginning <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&quot;http&quot;</code> or <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&quot;https&quot;</code> part of a URL?</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg http</span></div><div class="" style="color:#404040"><span class="">assets/cacert.pem</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">9</span><span class="">:</span><span class="" style="color:#9CA3AF;font-style:italic">## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">okhttp3/internal/publicsuffix/NOTICE</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">2</span><span class="">:https://publicsuffix.org/list/public_suffix_list.dat</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">5</span><span class="">:https://mozilla.org/MPL/2.0/</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">META-INF/services/io.grpc.ManagedChannelProvider</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">1</span><span class="">:io.grpc.okhttp.d</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">google/protobuf/source_context.proto</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">3</span><span class="">:// https://developers.google.com/protocol-buffers/</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">google/protobuf/empty.proto</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">[</span><span class="">many similar protobuf results omitted</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">META-INF/MANIFEST.MF</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">494</span><span class="">:Name: okhttp3/internal/publicsuffix/NOTICE</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">497</span><span class="">:Name: okhttp3/internal/publicsuffix/publicsuffixes.gz</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">META-INF/CERT.SF</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">495</span><span class="">:Name: okhttp3/internal/publicsuffix/NOTICE</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">498</span><span class="">:Name: okhttp3/internal/publicsuffix/publicsuffixes.gz</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">res/56.xml</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">2</span><span class="">:</span><span class="" style="color:#8B5CF6">&lt;</span><span class="">resources xmlns:tools</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#1bb3ff">&quot;http://schemas.android.com/tools&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">res/sd.xml</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">6</span><span class="">:</span><span class="" style="color:#8B5CF6">&lt;</span><span class="">resources xmlns:tools</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#1bb3ff">&quot;http://schemas.android.com/tools&quot;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, that&#x27;s interesting. &quot;okhttp3&quot;? This seems like it could be related to how the application makes HTTP requests to the API. But this still leaves us with a few questions:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why weren&#x27;t we able to find the URLs?</strong> From a bit of research, it looks like Android apps store Java code in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.dex</code> files: the Dalvik Executable Format. (Dalvik is the name of the virtual machine used to run Android apps.) It is possible that this format uses compression or other techniques which would prevent a literal string from appearing. Running <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rg</code> with the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-a</code> parameter does show some matches in a binary file, but they seem to be in a section of the file which just stores string literals, and based on the limited number of matches, it is likely that URLs are assembled at runtime (meaning we won&#x27;t find a fully-formed URL in the source).</p>
</li>
<li class="my-2 pl-2">
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why did the app refuse to connect via our monitoring setup?</strong> Here&#x27;s where we need to dig in to how OkHttp works a bit more.</p>
</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="certificate-pinning">Certificate Pinning</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://square.github.io/okhttp/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OkHttp</a> is a library made by Square for making HTTP requests on Android (or other Java platforms). The fact that it was developed by Square, a payment processing company, is a clue that they might be taking steps to secure the connection that most apps wouldn&#x27;t.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a snippet from the front page of the OkHttp documentation, emphasis mine:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses, OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and services hosted in redundant data centers. OkHttp supports modern TLS features (TLS 1.3, ALPN, <strong>certificate pinning</strong>). It can be configured to fall back for broad connectivity.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Certificate pinning sounds relevant to what we&#x27;re doing here, considering our approach is to supply an alternate certificate. So what is it? According to the <a href="https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OkHttp docs</a>:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Constrains which certificates are trusted. Pinning certificates defends against attacks on certificate authorities. <strong>It also prevents connections through man-in-the-middle certificate authorities either known or unknown to the application’s user.</strong> This class currently pins a certificate’s Subject Public Key Info as described on <a href="https://www.imperialviolet.org/2011/05/04/pinning.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adam Langley’s Weblog</a>. Pins are either base64 SHA-256 hashes as in HTTP Public Key Pinning (HPKP) or SHA-1 base64 hashes as in Chromium’s static certificates.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our monitoring setup is such a man-in-the-middle scenario: we use our own certificate authority (in this case, Burp Suite) to &quot;re-secure&quot; the actual connection from TransitApp, after we&#x27;ve monitored and tampered with the connection.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, how do we get around certificate pinning? We&#x27;ll need to get quite a bit more serious about our decompilation efforts, since we&#x27;ll need to remove the hash of the existing certificate in the code and replace that with the hash of our own certificate. I roughly followed <a href="https://fullstackhero.medium.com/bypass-okhttp-certificatepinner-on-android-a085b8074e25" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this guide</a> for this step.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="apk-analysis">APK Analysis</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Android Studio helpfully includes an <a href="https://developer.android.com/studio/debug/apk-analyzer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">APK Analyzer</a> tool which we can use to peek a bit deeper into the app. Create a new project,then drag and drop the TransitApp APK into the main window.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, method names have pretty much all been minified, so you&#x27;ll see a bunch of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">u4</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">a5</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">o4</code>, etc. Android Studio also doesn&#x27;t let us view or modify the Java code within each method. However, note that even the OkHttp3 code seems to be minified, and I couldn&#x27;t find any reference to CertificatePinner (although <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rg -a CertificatePinner</code> returned a few matches, so maybe there&#x27;s hope?)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An open-source tool called <a href="https://ibotpeaches.github.io/Apktool/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">APKtool</a> might save us. APKtool allows us to essentially decompile an APK into Smali (essentially an assembly listing for Java bytecode), make modifications, then recompile it. To start, let&#x27;s see if we can find the certificate hash.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">First, download the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.jar</code> from <a href="https://bitbucket.org/iBotPeaches/apktool/downloads/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>, then run it with:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">java -jar apktool_2.7.0.jar d Transit</span><span class="" style="color:#ff218c">\</span><span class=""> Bus</span><span class="" style="color:#ff218c">\</span><span class=""> </span><span class="" style="color:#ff218c">\</span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#ff218c">\</span><span class=""> Subway</span><span class="" style="color:#ff218c">\</span><span class=""> Times_5.13.5_Apkpure.apk</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, dive into the directory named after that APK. We&#x27;re looking for the code that invokes <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="reading-smali-bytecode-working-up">Reading Smali Bytecode: Working Up</h3>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">rg CertificatePinner</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This seems to give quite a few results within the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">okhttp3</code> library: the implementation of certificate pinning, special handling in the connection class, and a few other uses. However, there is one result outside that library:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/platform/AndroidPlatformModule2.smali</span></div><div class="" style="color:#404040"><span class="">91:.method private getCertificatePinner()Lokhttp3/CertificatePinner;</span></div><div class="" style="color:#404040"><span class="">107:    new-instance v1, Lokhttp3/CertificatePinner$a;</span></div><div class="" style="color:#404040"><span class="">111:    invoke-direct {v1}, Lokhttp3/CertificatePinner$a;-&gt;&lt;init&gt;()V</span></div><div class="" style="color:#404040"><span class="">206:    invoke-virtual {v1, v3, v4}, Lokhttp3/CertificatePinner$a;-&gt;a(Ljava/lang/String;[Ljava/lang/String;)Lokhttp3/CertificatePinner$a;</span></div><div class="" style="color:#404040"><span class="">215:    invoke-virtual {v1}, Lokhttp3/CertificatePinner$a;-&gt;b()Lokhttp3/CertificatePinner;</span></div><div class="" style="color:#404040"><span class="">250:    invoke-direct {p0}, Lcom/masabi/justride/sdk/platform/AndroidPlatformModule2;-&gt;getCertificatePinner()Lokhttp3/CertificatePinner;</span></div><div class="" style="color:#404040"><span class="">258:    invoke-virtual {p1, v0}, Lokhttp3/OkHttpClient$a;-&gt;d(Lokhttp3/CertificatePinner;)Lokhttp3/OkHttpClient$a;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The result on line <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">111</code> looks perhaps the most interesting to us: invoking the constructor on the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner</code> class. However, it doesn&#x27;t seem to be passing in any sort of hash. Let&#x27;s consult the OkHttp <a href="https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">documentation</a> as to how <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner</code>s are constructed:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#404040">String</span><span class=""> hostname </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;publicobject.com&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">CertificatePinner</span><span class=""> certificatePinner </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">CertificatePinner</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#404040">Builder</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">add</span><span class="" style="color:#ff218c">(</span><span class="">hostname</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">build</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">OkHttpClient</span><span class=""> client </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">OkHttpClient</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#404040">Builder</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">certificatePinner</span><span class="" style="color:#ff218c">(</span><span class="">certificatePinner</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">build</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">Request</span><span class=""> request </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Request</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#404040">Builder</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">url</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;https://&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> hostname</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">build</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">client</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">newCall</span><span class="" style="color:#ff218c">(</span><span class="">request</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">execute</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Huh, okay, so we&#x27;re really looking for a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner.Builder</code>. But <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rg CertificatePinner.Builder</code> gives no results.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s where a Java quirk comes into play: Nested classes like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner.Builder</code> are represented internally using a dollar sign in place of the dot. So we&#x27;re really looking for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner$Builder</code>. Make sure to escape it properly:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">rg </span><span class="" style="color:#1bb3ff">&#x27;CertificatePinner\$Builder&#x27;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And... still no results. But don&#x27;t lose hope yet--remember our experiments with APK Analyzer? Perhaps the name <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Builder</code> got minified. Let&#x27;s try to find any nested classes within <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">rg </span><span class="" style="color:#1bb3ff">&#x27;CertificatePinner\$&#x27;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It looks like there&#x27;s a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner$a</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner$b</code>, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner$c</code>. The <a href="https://square.github.io/okhttp/4.x/okhttp/okhttp3/-certificate-pinner/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">docs</a> seem to show three nested classes: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Builder</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Companion</code>, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Pin</code>. Out of the three, it seems like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Builder</code> is the only one that should need to be used externally. Looking at the one result outside of the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">okhttp3</code> package:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/platform/AndroidPlatformModule2.smali</span></div><div class="" style="color:#404040"><span class="">107:    new-instance v1, Lokhttp3/CertificatePinner$a;</span></div><div class="" style="color:#404040"><span class="">111:    invoke-direct {v1}, Lokhttp3/CertificatePinner$a;-&gt;&lt;init&gt;()V</span></div><div class="" style="color:#404040"><span class="">206:    invoke-virtual {v1, v3, v4}, Lokhttp3/CertificatePinner$a;-&gt;a(Ljava/lang/String;[Ljava/lang/String;)Lokhttp3/CertificatePinner$a;</span></div><div class="" style="color:#404040"><span class="">215:    invoke-virtual {v1}, Lokhttp3/CertificatePinner$a;-&gt;b()Lokhttp3/CertificatePinner;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;re looking for the call to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Builder.add()</code>, since that will pass in the pin as a hash. Again, the name <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.add()</code> will be minified, so we&#x27;ll need to be clever. Now that we&#x27;ve narrowed things down to a single file, we can start to read through and look for anything interesting. This method stands out (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.line</code> directives omitted for brevity):</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">private</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">getCertificatePinner</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">8</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    iget-object </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">platform</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidPlatformModule2</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">sdkConfiguration</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    new-instance </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner$a</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner$a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-interface </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">iterator</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Iterator</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">goto_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    invoke-interface </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Iterator</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">hasNext</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">Z</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    if-eqz </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-interface </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Iterator</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">next</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Object</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    check-cast </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    iget-object </span><span class="" style="color:#ff218c">v3</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">platform</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidPlatformModule2</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">sdkConfiguration</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v3</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getHostname</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v3</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    const/4 </span><span class="" style="color:#ff218c">v4</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    new-array </span><span class="" style="color:#ff218c">v4</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v4</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    const/4 </span><span class="" style="color:#ff218c">v5</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    new-instance </span><span class="" style="color:#ff218c">v6</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v6</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    const-string </span><span class="" style="color:#ff218c">v7</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;sha256/&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v6</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v7</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">append</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v6</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">append</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v6</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">StringBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">toString</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    aput-object </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v4</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v5</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v3</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v4</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner$a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">a</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner$a</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    goto </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">goto_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner$a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">b</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">okhttp3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">CertificatePinner</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    return-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">method</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s step through what this is doing, using our intuition to bridge the gaps:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">Calling <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfiguration.getCertificatePins()</code>, which returns a list of some type (maybe Strings?)</li>
<li class="my-2 pl-2">Creating a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner.Builder</code> (shown here as a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner$a</code>)</li>
<li class="my-2 pl-2">Iterating through the list of certificate pins</li>
<li class="my-2 pl-2">For each pin, using a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">StringBuilder</code> to assemble a hash string (starting with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">sha256/</code>)</li>
<li class="my-2 pl-2">Calling <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.add()</code> on the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">CertificatePinner.Builder</code> object with the constructed string for each pin</li>
<li class="my-2 pl-2">Returning the result of calling <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.build()</code> on the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Builder</code></li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This means we&#x27;ll need to search a little bit deeper to find the hashes we seek, starting with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">getCertificatePins()</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg getCertificatePins</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/converters/config/SdkConfigurationConverter.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">454</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">p1</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">getCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/internal/models/config/SdkConfiguration.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">762</span><span class="">:.method public getCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/platform/AndroidPlatformModule2.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">99</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">v0</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">getCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The result with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.method public</code> is the definition of the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">getCertificatePins()</code> method -- let&#x27;s start there.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">getCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.annotation</span><span class=""> </span><span class="" style="color:#8B5CF6">system</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">dalvik</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">annotation</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Signature</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        value </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;()&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">annotation</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    iget-object </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">certificatePins</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    return-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">method</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, so we&#x27;re just returning the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">certificatePins</code> field. It&#x27;s just a classic &quot;getter method.&quot; We can track down the field definition:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.field</span><span class=""> </span><span class="" style="color:#8B5CF6">private</span><span class=""> </span><span class="" style="color:#8B5CF6">final</span><span class=""> </span><span class="" style="color:#ff218c">certificatePins</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.annotation</span><span class=""> </span><span class="" style="color:#8B5CF6">system</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">dalvik</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">annotation</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Signature</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        value </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">annotation</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">field</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, so it&#x27;s a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">private final List&lt;String&gt;</code>. That&#x27;s pretty standard. This essentially gives us two options: either these options are set in the constructor, or they&#x27;re added later through another public method. Let&#x27;s check the constructor first.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">private</span><span class=""> </span><span class="" style="color:#8B5CF6">constructor</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">Z</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">Z</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.annotation</span><span class=""> </span><span class="" style="color:#8B5CF6">system</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">dalvik</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">annotation</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Signature</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        value </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;(&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Z&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Z)V&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">annotation</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Oh god, that&#x27;s 32 lines and we haven&#x27;t even gotten to an implementation yet. Here&#x27;s the part of the implementation that deals with the certificate pins:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">move-object </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p4</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">iput-object </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">certificatePins</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So the pins are passed in as a list, in the fifth parameter (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">p4</code> is indexed starting at zero.) Let&#x27;s keep following this wild goose chase: where is the constructor called?</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg </span><span class="" style="color:#1bb3ff">&#x27;SdkConfiguration;-&gt;&lt;init&gt;&#x27;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/internal/models/config/SdkConfiguration.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">211</span><span class="">:    invoke-direct/range </span><span class="" style="color:#ff218c">{</span><span class="">p0 </span><span class="" style="color:#ff218c">..</span><span class=""> p18</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#8B5CF6">&lt;</span><span class="">init</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#ff218c">(</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">ZLjava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Z</span><span class="" style="color:#ff218c">)</span><span class="">V</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$Builder</span><span class="">.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">379</span><span class="">:    invoke-direct/range </span><span class="" style="color:#ff218c">{</span><span class="">v2 </span><span class="" style="color:#ff218c">..</span><span class=""> v21</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#8B5CF6">&lt;</span><span class="">init</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#ff218c">(</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">ZLjava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">Ljava/lang/String</span><span class="" style="color:#ff218c">;</span><span class="">ZLcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$1</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">V</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Oh, duh, it&#x27;s another builder. At least this one isn&#x27;t minified? Let&#x27;s take a look at the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfiguration$Builder.smali</code> file. Alongside brand, country code, and other parameters, we see a few points of interest.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s the definition of the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">certificatePins</code> field on the <em>builder</em>. Note that it isn&#x27;t marked <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">final</code>, meaning it probably gets assigned to somewhere.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.field</span><span class=""> </span><span class="" style="color:#8B5CF6">private</span><span class=""> </span><span class="" style="color:#ff218c">certificatePins</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.annotation</span><span class=""> </span><span class="" style="color:#8B5CF6">system</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">dalvik</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">annotation</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Signature</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        value </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">annotation</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">field</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next in the file is the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.build()</code> method. It does some checks for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">null</code>, but other than that, seems pretty boring. Scrolling down a bit, though, we see a definition for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">setCertificatePins()</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">setCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.annotation</span><span class=""> </span><span class="" style="color:#8B5CF6">system</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">dalvik</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">annotation</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Signature</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        value </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;(&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/util/List&lt;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Ljava/lang/String;&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;&gt;;)&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&quot;Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration$Builder;&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">annotation</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    iput-object </span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">internal</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">models</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">config</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SdkConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">certificatePins</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">List</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    return-object </span><span class="" style="color:#ff218c">p0</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">method</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There&#x27;s no surprise here regarding what this method does (it just assigns the argument to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">certificatePins</code> field). However, now that we know the name of this method, we can try to look for it elsewhere.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg setCertificatePins</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/converters/config/SdkConfigurationConverter.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">199</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">v2, v3</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$Builder</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">setCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$Builder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$Builder</span><span class="">.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">770</span><span class="">:.method public setCertificatePins</span><span class="" style="color:#ff218c">(</span><span class="">Ljava/util/List</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/internal/models/config/SdkConfiguration</span><span class="" style="color:#ff218c">$Builder</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It looks like the only place <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">setCertificatePins</code> is called is within <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfigurationConverter</code>. This is an interesting class, and it&#x27;s not immediately clear what it&#x27;s doing. A few method names give us a clue, however:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">convertJSONObjectToModel(JSONObject p1)</code></li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">convertModelToJSONObject(SdkConfiguration p1)</code></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It would be <em>really</em> easy if the certificate pins were just stored in a JSON file somewhere... But <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">find . -type f -name &quot;*.json&quot;</code> comes up empty.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One quick sanity check: Is <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfigurationConverter</code> even constructed? It could be that we&#x27;re looking in the complete wrong part of the code here. Maybe our assumption about certificate pinning isn&#x27;t correct after all?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Note that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfigurationConverter</code> has a private constructor and a public static <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">create()</code> method. Therefore, we should be searching for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfigurationConverter.create()</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg </span><span class="" style="color:#1bb3ff">&#x27;SdkConfigurationConverter;-&gt;create&#x27;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/jobs/config/ProcessConfigurationDataJob.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">1096</span><span class="">:    invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/converters/config/SdkConfigurationConverter</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">create</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/converters/config/SdkConfigurationConverter</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, so it&#x27;s used <em>somewhere</em> at least. Is <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ProcessConfigurationDataJob</code> invoked anywhere? It looks like it has a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">create()</code> method, just like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SdkConfigurationConverter</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg </span><span class="" style="color:#1bb3ff">&#x27;ProcessConfigurationDataJob;-&gt;create&#x27;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/masabi/justride/sdk/AndroidJustRideSdkBuilder.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">121</span><span class="">:    invoke-static </span><span class="" style="color:#ff218c">{</span><span class="">v0</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/jobs/config/ProcessConfigurationDataJob</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">create</span><span class="" style="color:#ff218c">(</span><span class="">Lcom/masabi/justride/sdk/platform/crypto/PlatformSignatureVerifier</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/jobs/config/ProcessConfigurationDataJob</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And just to finish going up the chain, where is this instantiated?</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$ rg AndroidJustRideSdkBuilder</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">..</span><span class="">.</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">smali_classes2/com/thetransitapp/droid/shared/TransitApp.smali</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">524</span><span class="">:    invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/AndroidJustRideSdk</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">builder</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">532</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">p1, p0</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">application</span><span class="" style="color:#ff218c">(</span><span class="">Landroid/app/Application</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">540</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">p1, v0</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">configuration</span><span class="" style="color:#ff218c">(</span><span class="">Ljava/io/InputStream</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">548</span><span class="">:    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="">p1</span><span class="" style="color:#ff218c">}</span><span class="">, Lcom/masabi/justride/sdk/AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class="">-</span><span class="" style="color:#8B5CF6">&gt;</span><span class="">build</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="">Lcom/masabi/justride/sdk/AndroidJustRideSdk</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">..</span><span class="">.</span><span class="" style="color:#ff218c">]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here we are: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">TransitApp.smali</code>, which seems like the entrypoint to the application. It seems like it passes some sort of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">InputStream</code> to the builder--maybe this is the JSON data we&#x27;re looking for?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s the method that invokes this (it&#x27;s just labeled <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">j</code>, since we&#x27;re back into minified code):</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">private</span><span class=""> </span><span class="" style="color:#8B5CF6">synthetic</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">j</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Throwable</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">2</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    if-eqz </span><span class="" style="color:#ff218c">p2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    return-void</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    const/4 </span><span class="" style="color:#ff218c">p2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_start_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    new-instance </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">io</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">ByteArrayInputStream</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getBytes</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">B</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">p1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    const/4 </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Base64</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">decode</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">BI</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">B</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">p1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">io</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">ByteArrayInputStream</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">B</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_end_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.catch</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Exception</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_start_0</span><span class=""> </span><span class="" style="color:#8B5CF6">..</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_end_0</span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">catch_1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.catchall</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_start_0</span><span class=""> </span><span class="" style="color:#8B5CF6">..</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_end_0</span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">catchall_1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">try_start_1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdk</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">builder</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">p1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">application</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">app</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Application</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    move-result-object </span><span class="" style="color:#ff218c">p1</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">configuration</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">io</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">InputStream</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">masabi</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">justride</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">sdk</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">AndroidJustRideSdkBuilder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#8B5CF6">..</span><span class="">.]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, so <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">v0</code> is our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">InputStream</code>. We call <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">getBytes()</code> on the string parameter, then Base64 decode it, then pass that into the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ByteArrayInputStream</code> constructor. So where is the string passed into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">j</code>? Searching just that file (assuming it&#x27;s the entry point) for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-&gt;j</code> gives another method, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">b</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#8B5CF6">static</span><span class=""> </span><span class="" style="color:#8B5CF6">synthetic</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">b</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Throwable</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p2</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">j</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Throwable</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    return-void</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.end</span><span class=""> </span><span class="" style="color:#8B5CF6">method</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It looks like this is a <a href="https://www.baeldung.com/java-synthetic#1-bridge-methods" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">bridge method</a>, used to support generics.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, is this method called anywhere? Doing a regex search in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">invoke-static \{.*\}, Lcom/thetransitapp/droid/shared/TransitApp</code> doesn&#x27;t find anything related to it.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="reading-smali-bytecode-working-down">Reading Smali Bytecode: Working Down</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Maybe we missed something somewhere. Doing a bit of <a href="https://stackoverflow.com/questions/10057448/entrypoint-of-android-application" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">research</a>, it looks like the entrypoint to an Android application is in a class that inherits from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Application</code> and overrides the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">onCreate</code> method. Does our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">TransitApp</code> class fit the bill? Let&#x27;s check. Right at the top of the file, we see:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.class</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.super</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">app</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Application</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.source</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;TransitApp.java&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic"># interfaces</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.implements</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">ac</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">b</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, so that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.super</code> line confirms it. Let&#x27;s look at the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">onCreate()</code> method. It&#x27;s quite long, so let&#x27;s break it down.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.method</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">onCreate</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">.locals</span><span class=""> </span><span class="" style="color:#8B5CF6">4</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    invoke-super </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">app</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Application</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">onCreate</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We start by calling the base <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Application</code>&#x27;s implementation of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">onCreate()</code> -- pretty standard stuff for inheritance. We also have four local variables.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">c</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">a</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">j</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">const/4 </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">if-eqz </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">c</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">I</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">d</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">Z</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">new-instance </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp$a</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp$a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">b</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$e</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">g</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e$c</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">androidx</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">emoji2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">text</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">e</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">:</span><span class="" style="color:#8B5CF6">cond_0</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://developer.android.com/jetpack/androidx" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AndroidX</a>, also known as Jetpack, is a set of Android libraries provided by Google to handle common tasks. <a href="https://developer.android.com/jetpack/androidx/releases/emoji2" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">androidx.emoji2</code></a> is a library to support modern emoji on older platforms, including text rendering and emoji pickers. The method calls here are minified, but we can safely rule this out as uninteresting for now. That said, the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">if</code> statement that seems to construct a new instance of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">TransitApp</code> definitely strikes me as odd.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getCacheDir</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">io</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">File</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">data</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">NetworkHandler</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">setCacheDir</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">io</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">File</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This seems to be setting the directory to store cached assets.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">mb</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">a</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SharedPreferences</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">iput-object </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c">a</span><span class="" style="color:#ff218c">:</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">SharedPreferences</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This code uses the <a href="https://developer.android.com/reference/android/content/SharedPreferences" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">SharedPreferences</a> class in some form, likely to retrieve some form of user preferences and store a handle to them for later.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">j2</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">c</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">I</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getTheme</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">res</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Resources$Theme</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">res</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Resources$Theme</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">applyStyle</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">IZ</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This handles differentiating between dark and light theme.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">TransitApp</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">d</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">z2</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">i</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">thetransitapp</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">droid</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">shared</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">util</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">z2</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">g</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">google</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">firebase</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">crashlytics</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">FirebaseCrashlytics</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">getInstance</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">google</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">firebase</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">crashlytics</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">FirebaseCrashlytics</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">google</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">firebase</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">crashlytics</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">FirebaseCrashlytics</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">setCrashlyticsCollectionEnabled</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">Z</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">google</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">firebase</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">crashlytics</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">FirebaseCrashlytics</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">setUserId</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This code grabs some sort of user ID, stores it in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">v0</code>, then uses it to set up the <a href="https://firebase.google.com/docs/crashlytics" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Firebase Crashlytics</a> crash reporter.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">const-string </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;com.thetransitapp&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">filled-new-array </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">z2</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">b</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">a</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">[</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">new-instance </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">b</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">d</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">c</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">a</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">a</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v2</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">f0</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">k</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">const-string </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;3687b056476e15e4fe1b346e559a4169&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">A</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">q</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">r</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">app</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Application</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v1</span><span class=""></span></div><div class="" style="color:#404040"><span class="">const-wide/32 </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0xea60</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v3</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">c0</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">J</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">d</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Checking the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">a3/a.smali</code> file, we get this header:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">.class</span><span class=""> </span><span class="" style="color:#8B5CF6">public</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">a3</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">a</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.super</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Object</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">.source</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Amplitude.java&quot;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://www.docs.developers.amplitude.com/data/sdks/android/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Amplitude</a> looks like some kind of analytics application, which makes sense for something that would be set up in an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">onCreate()</code> call.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">const/4 </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0x0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Purchases</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">setDebugLogsEnabled</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">Z</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">new-instance </span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">const-string </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;JfhIYqEpBxRrLkgLLizTDhRqyoPguWdY&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-direct </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">p0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v2</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">&lt;init&gt;</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">android</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">content</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Context</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">V</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">appUserID</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">java</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">lang</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">String</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-virtual </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration$Builder</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">build</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">move-result-object </span><span class="" style="color:#ff218c">v0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">invoke-static </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">v0</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Purchases</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="" style="color:#ff218c;font-style:italic">configure</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">PurchasesConfiguration</span><span class="" style="color:#ff218c">;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#1bb3ff">L</span><span class="" style="color:#8B5CF6">com</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">revenuecat</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#8B5CF6">purchases</span><span class="" style="color:#ff218c">/</span><span class="" style="color:#404040">Purchases</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This code sets up the <a href="https://www.revenuecat.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RevenueCat</a> integration for in-app subscriptions.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The method continues on, but nothing else in it looks all that notable -- just some error handling stuff. But looking into each of these integrations is making me realize: what exactly were we looking at before?</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="so-what-exactly-is-justride">So, what exactly is JustRide?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Remember, we first found our JustRide method in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">com/masabi/justride/sdk/platform/AndroidPlatformModule2.smali</code>. Are we sure that this is related to what we&#x27;re trying to find?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Looking up <a href="https://www.justride.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">JustRide</a>, it advertizes itself as a mobile ticketing platform. They provide <a href="https://www.masabi.com/justride-mobile-ticketing-sdk/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">an SDK</a> for integrating ticket purchases into other apps. It seems like TransitApp just includes the JustRide SDK for this functionality, so its use of certificate pinning is largely irrelevant.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Okay, back to the drawing board.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="network-security-config">Network Security Config</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Doing a bit more research, I stumbled upon a GitHub project called <a href="https://github.com/levyitay/AddSecurityExceptionAndroid" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AddSecurityExceptionAndroid</a> that claims to be able to enable reverse engineering. It links to a few Android reference pages.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">First, it mentions <a href="https://developer.android.com/training/articles/security-config.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Network Security Configuration</a>, which allows application developers to implement certificate pinning using a configuration file instead of any code changes. Maybe this is what we&#x27;re after? It gives an example <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AndroidManifest.xml</code> file for this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">manifest</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">...</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">application</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">networkSecurityConfig</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@xml/network_security_config</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"></span></div><div class="" style="color:#404040"><span class="" style="color:#ff218c">                    </span><span class="" style="color:#1bb3ff;font-style:italic">...</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        ...</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">application</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">manifest</span><span class="" style="color:#ff218c">&gt;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s check this against TransitApp&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AndroidManifest.xml</code> to see if it uses this. I&#x27;ve grabbed only the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;application&gt;</code> tag, since the file is quite large:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">application</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">allowBackup</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">true</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">appComponentFactory</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">androidx.core.app.CoreComponentFactory</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">extractNativeLibs</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">false</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">fullBackupContent</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@xml/backup_descriptor</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">icon</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@mipmap/ic_launcher</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">label</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@string/app_name</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">largeHeap</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">true</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">localeConfig</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@xml/locales_config</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">logo</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@drawable/action_bar_icon</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">name</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">com.thetransitapp.droid.shared.TransitApp</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">roundIcon</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@mipmap/ic_launcher_round</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">theme</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">@style/SplashScreen</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6;font-style:italic">android:</span><span class="" style="color:#1bb3ff;font-style:italic">usesCleartextTraffic</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">true</span><span class="" style="color:#ff218c">&quot;</span><span class="" style="color:#ff218c">&gt;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I don&#x27;t see a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">networkSecurityConfig</code> option, and furthermore, the app specifically opts into &quot;cleartext traffic&quot; (HTTP). Let&#x27;s keep looking.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The other linked source is a <a href="https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">blog post from 2016</a>. Emphasis mine:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In Android Nougat, we’ve changed how Android handles trusted certificate authorities (CAs) to provide safer defaults for secure app traffic. Most apps and users should not be affected by these changes or need to take any action. The changes include:</p>
</blockquote>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Safe and easy APIs to trust custom CAs.</li>
<li class="my-2 pl-2"><strong>Apps that target API Level 24 and above no longer trust user or admin-added CAs for secure connections, by default.</strong></li>
<li class="my-2 pl-2">All devices running Android Nougat offer the same standardized set of system CAs—no device-specific customizations.</li>
</ul></div>
</blockquote>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For more details on these changes and what to do if you’re affected by them, read on.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is the exact approach we were trying: adding our own CA to try to pull off a man-in-the-middle attack on ourselves.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Reading further in the blog post, it seems like apps need to use a Network Security Config setting to opt into user-added CAs. Since TransitApp does not supply a Network Security Config, this is probably what&#x27;s blocking us from using our own CA here.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="addsecurityexceptionandroid">AddSecurityExceptionAndroid</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AddSecurityExceptionAndroid</code> script works by overwriting the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">network_security_config.xml</code> file and adding the relevant attribute to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">application</code> tag in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AndroidManifest.xml</code>. Let&#x27;s run it on our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.apk</code> and see if it works.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">First, set up a debug keystore in Android Studio, following <a href="https://developer.android.com/studio/publish/app-signing#generate-key" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">the documentation</a>. I chose the following settings based on what I found in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">addSecurityExceptions.sh</code> file:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Setting</th><th class="border border-black p-2 dark:border-white">Value</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">Key store path</td><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/Users/breq/.android/keystore.jks</code></td></tr><tr><td class="border border-gray-500 p-2">Password</td><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">android</code></td></tr><tr><td class="border border-gray-500 p-2">Alias</td><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">androiddebugkey</code></td></tr><tr><td class="border border-gray-500 p-2">Password</td><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">android</code></td></tr><tr><td class="border border-gray-500 p-2">Validity</td><td class="border border-gray-500 p-2">25 (default)</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After creating the key, you can exit out of Android Studio.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">git</span><span class=""> clone https://github.com/levyitay/AddSecurityExceptionAndroid</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">cd</span><span class=""> AddSecurityExceptionAndroid</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">cp</span><span class=""> ~/Downloads/Transit</span><span class="" style="color:#ff218c">\</span><span class=""> Bus</span><span class="" style="color:#ff218c">\</span><span class=""> </span><span class="" style="color:#ff218c">\</span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#ff218c">\</span><span class=""> Subway</span><span class="" style="color:#ff218c">\</span><span class=""> Times_5.13.5_Apkpure.apk ./transit.apk</span></div><div class="" style="color:#404040"><span class="">./addSecurityExceptions.sh -d transit.apk</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-d</code> to make the .apk debuggable -- I don&#x27;t know if this will be useful or not, but there&#x27;s no reason <em>not</em> to.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When running this, I ran into this issue:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">W: /tmp/transit/AndroidManifest.xml:82: error: attribute android:localeConfig not found.</span></div><div class="" style="color:#404040"><span class="">W: error: failed processing manifest.</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Based on this <a href="https://github.com/iBotPeaches/Apktool/issues/2807" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GitHub issue</a>, it seems like I need to make modifications to the APK before attempting to recompile it. I modified the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">addSecurityException.sh</code> script to wait before recompiling:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">@@ -122,6 +122,9 @@ if [ $makeDebuggable ] &amp;&amp; ! grep -q &quot;debuggable&quot; &quot;$tmpDir/AndroidManifest.xml&quot;;</span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">  mv &quot;$tmpDir/AndroidManifest.xml.new&quot; &quot;$tmpDir/AndroidManifest.xml&quot;</span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">fi</span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic">echo &quot;Make any changes now in $tmpDir&quot;</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic">echo &quot;Press ENTER when done...&quot;</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic">read</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">java -jar &quot;$DIR/apktool.jar&quot;  --use-aapt2 empty-framework-dir --force &quot;$tmpDir&quot;</span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">echo &quot;Building temp APK $tempFileName&quot;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, in another terminal, I opened the temporary directory (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/tmp/transit</code> in my case). I didn&#x27;t see a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">locales_config.xml</code>, but I did notice that the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AndroidManifest.xml</code> included the parameter <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">android:localeConfig=&quot;@xml/locales_config&quot;</code>. I removed the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">localeConfig</code> parameter from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AndroidManifest.xml</code>, then continued the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">addSecurityException.sh</code> script, which worked this time.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="dynamic-analysis-take-2">Dynamic Analysis: Take 2</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ll need to uninstall the old app from our emulator before installing the APK. Boot up the emulated device and uninstall TransitApp, then drag and drop the new APK onto the emulator.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Doing this, I get an error: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">INSTALL_FAILED_NO_MATCHING_ABI: Failed to extract native libraries</code>. Actually, when I try to use the unmodified <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.apk</code> I downloaded, I get the same error. Looking into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/tmp/transit/lib</code>, we only see <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">x86_64</code> -- the sketchy website lied about what architecture the APK is.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can try <a href="https://www.apkmirror.com/apk/transit-app-inc/transit-real-time-transit-app/transit-real-time-transit-app-5-14-6-release/transit-bus-subway-times-5-14-6-6-android-apk-download/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">another sketchy site</a>. Looking in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/tmp/transit/lib</code> now, we see <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">arm64-v8a</code> -- perfect. We can verify that this APK installs correctly. (Make sure you have internet access in your emulator -- I forgot to launch <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dnsmasq</code> and had some issues with this.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can go through the same steps of running the script and removing the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">android:localeConfig</code> parameter. Install this APK, and boom: our modified TransitApp build is running in our emulator. Some parts of the app don&#x27;t seem to be working: maybe this is because our proxy isn&#x27;t running?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Start the proxy, verify the settings in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/hosts</code>, and restart <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dnsmasq</code> for good measure.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Holy shit. We&#x27;re finally getting something.</em></p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="heres-where-the-fun-begins">Here&#x27;s Where The Fun Begins</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can already see some requests just from opening the app and signing in. Here&#x27;s the response for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/v3/users/me</code> right now:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">HTTP/2</span><span class=""> </span><span class="" style="color:#8B5CF6">200</span><span class=""> </span><span class="" style="color:#1bb3ff">OK</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">X-Powered-By</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Express</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Type</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">application/json; charset=utf-8</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Length</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">298</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Etag</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">W/&quot;12a-pknx7teDN3n3f6xhsnm1RmASA/M&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Vary</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Accept-Encoding</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Date</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Fri, 16 Jun 2023 22:29:36 GMT</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Via</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">1.1 google</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Alt-Svc</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">h3=&quot;:443&quot;; ma=2592000,h3-29=&quot;:443&quot;; ma=2592000</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">{&quot;subscriptions&quot;:[],&quot;royale&quot;:{&quot;user_id&quot;:&quot;9702c35a82984e76&quot;,&quot;avatar&quot;:{&quot;username&quot;:&quot;Floppy Sensei&quot;,&quot;username_type&quot;:&quot;generated&quot;,&quot;image_id&quot;:&quot;💾&quot;,&quot;color&quot;:&quot;e3131b&quot;,&quot;foreground_color&quot;:&quot;ffffff&quot;,&quot;visibility&quot;:&quot;public&quot;}},&quot;main_agencies&quot;:[&quot;MBTA&quot;],&quot;time_zone_name&quot;:&quot;America/New_York&quot;,&quot;time_zone_delta&quot;:&quot;-0400&quot;}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s where we can confirm our hypothesis about the emoji shuffle being done client-side. Looking at the network logs while clicking &quot;shuffle,&quot; nothing seems to be happening in the network console. Perfect!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ll edit our username and icon by accepting one of the suggestions. Here&#x27;s the generated request (I&#x27;ve changed the User ID and removed the authorization token):</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">PATCH</span><span class=""> </span><span class="" style="color:#1bb3ff">/v3/users/b1dfb2047e8bd5eb</span><span class=""> </span><span class="" style="color:#8B5CF6">HTTP/2</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Host</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">api.transitapp.com</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Language</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">en-US</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Authorization</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Basic [REDACTED]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Transit-Hours-Representation</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">12</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">User-Agent</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Transit/20900 transitLib/114 Android/13 Device/sdk_gphone64_arm64 Version/5.14.6</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Type</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">application/json</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Length</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">196</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Connection</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Keep-Alive</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Encoding</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">gzip, deflate</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;avatar&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;f3a4ba&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;foreground_color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;804660&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_id&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;🍦&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;emoji&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;subscribed&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#8B5CF6">false</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;Cone Extravaganza&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;generated&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;visibility&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;public&quot;</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And here&#x27;s the response:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">HTTP/2</span><span class=""> </span><span class="" style="color:#8B5CF6">200</span><span class=""> </span><span class="" style="color:#1bb3ff">OK</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">X-Powered-By</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Express</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Type</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">application/json; charset=utf-8</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Length</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">180</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Etag</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">W/&quot;b4-0vP9yEjHVjjz1iTkW1gCoYEcQG4&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Vary</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Accept-Encoding</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Date</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Fri, 16 Jun 2023 22:32:01 GMT</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Via</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">1.1 google</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Alt-Svc</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">h3=&quot;:443&quot;; ma=2592000,h3-29=&quot;:443&quot;; ma=2592000</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">{&quot;id&quot;:&quot;b1dfb2047e8bd5eb&quot;,&quot;avatar&quot;:{&quot;color&quot;:&quot;f3a4ba&quot;,&quot;foreground_color&quot;:&quot;804660&quot;,&quot;username&quot;:&quot;Cone Extravaganza&quot;,&quot;username_type&quot;:&quot;generated&quot;,&quot;image_id&quot;:&quot;🍦&quot;,&quot;visibility&quot;:&quot;public&quot;}}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s modify the request a bit. Turn on Burp&#x27;s Intercept tool, then randomize the name and icon again:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">PATCH</span><span class=""> </span><span class="" style="color:#1bb3ff">/v3/users/b1dfb2047e8bd5eb</span><span class=""> </span><span class="" style="color:#8B5CF6">HTTP/2</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Host</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">api.transitapp.com</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Language</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">en-US</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Authorization</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Basic [REDACTED]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Transit-Hours-Representation</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">12</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">User-Agent</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Transit/20900 transitLib/114 Android/13 Device/sdk_gphone64_arm64 Version/5.14.6</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Type</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">application/json</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Length</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">191</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Connection</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Keep-Alive</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Encoding</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">gzip, deflate</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;avatar&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;ffce00&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;foreground_color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;855323&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_id&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;😐&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;emoji&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;subscribed&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#8B5CF6">false</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;Feral Voovie&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;generated&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;visibility&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;public&quot;</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s quickly swap out the payload for this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">PATCH</span><span class=""> </span><span class="" style="color:#1bb3ff">/v3/users/b1dfb2047e8bd5eb</span><span class=""> </span><span class="" style="color:#8B5CF6">HTTP/2</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Host</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">api.transitapp.com</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Language</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">en-US</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Authorization</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Basic [REDACTED]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Transit-Hours-Representation</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">12</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">User-Agent</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Transit/20900 transitLib/114 Android/13 Device/sdk_gphone64_arm64 Version/5.14.6</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Type</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">application/json</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Content-Length</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">191</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Connection</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">Keep-Alive</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">Accept-Encoding</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="">gzip, deflate</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;avatar&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#8B5CF6">&quot;color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;ff42a1&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;foreground_color&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;000000&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_id&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;🎈&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;image_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;emoji&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;subscribed&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#8B5CF6">false</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;Brooke Chalmers&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;username_type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;generated&quot;</span><span class="" style="color:#ff218c">,</span><span class="" style="color:#8B5CF6">&quot;visibility&quot;</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#1bb3ff">&quot;public&quot;</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Things didn&#x27;t update in the UI. However, if we sign out and sign back in... There we go!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1080" height="2400" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ftransitapp%2Fsuccess.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Ftransitapp%2Fsuccess.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ftransitapp%2Fsuccess.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thanks for joining me on this little adventure! I worked on this blog post every now and then over the course of a few months. Getting this to work in the end was such a great feeling -- if my initial hypothesis had been wrong, I still would&#x27;ve learned a lot, but the payoff would&#x27;ve been quite a bit less fun. And if you see a 🎈 emoji rider around Boston, feel free to say hi!</p></div>]]></description>
            <link>https://breq.dev/2023/06/16/transitapp-reversing</link>
            <guid isPermaLink="false">/2023/06/16/transitapp-reversing</guid>
            <category><![CDATA[android]]></category>
            <category><![CDATA[emulation]]></category>
            <pubDate>Fri, 16 Jun 2023 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Emulation Project - Call for Collaborators!]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m trying to emulate a bunch of 6502-based systems -- the Commodore PET, VIC-20, and 64, the Apple IIe, and the NES. I&#x27;m writing it in Rust, currently targeting desktop and WebAssembly but with plans to support mobile and embedded, too. Right now, I&#x27;ve got the PET working, and you can try it out <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/noentiendo/index.html">here</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is an ambitious project, and I&#x27;m seeing some exciting results, but <strong>I need your help</strong> if I&#x27;m going to have a chance at getting all of this working within a reasonable timeframe. If you like Rust, are interested in old hardware, and might have some free time soon, <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/contact">let me know</a>.</p>
<div class="contents break-inside-avoid print:block"><img alt="A screenshot of a Commodore PET running BASIC." loading="lazy" width="1504" height="1080" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fnoentiendo%2Fpet.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fnoentiendo%2Fpet.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fnoentiendo%2Fpet.png&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-premise">The Premise</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The MOS 6502 defines an era of retro computing. It was a powerful chip, priced incredibly low. This same chip was used in the Commodore 64, Apple IIe, BBC Micro, and others, and a knockoff (with an entirely compatible instruction set) was used in the Nintendo Entertainment System. Many of these computers also relied on the MOS 6520 (Peripheral Interface Adapter, or PIA) and the MOS 6522 (Versatile Interface Adapter, or VIA).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Rust, like C or C++, is notable for its wide range of potential platforms. Toolchains exist for desktop, web, mobile, and even embedded processors, leaving open the possibility of creating a handheld device built to run this emulator.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By writing an emulator that works off of a few basic building blocks -- the 6502, the PIA and VIA, some basic RAM and ROM, and some special functions like the VIC chip -- it should be possible to emulate a wide range of systems, sharing much of the code between them. And by implementing this in Rust and targeting a variety of platforms, it should be possible to emulate anything, anywhere.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-work-so-far">The Work So Far</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you prefer reading code, take a look at <a href="https://github.com/breqdev/noentiendo" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">the repo</a>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--system-brooke"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--system brooke</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first system implementation was just something I came up with for testing. A <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">System</code> represents the 6502 and some attached <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Memory</code>, which in this case was just RAM from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x0000</code> through <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x3FFF</code>, some memory-mapped I/O at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x4000</code>, and just ROM from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x8000</code> to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0xFFFF</code>. I chose this configuration to be similar to <a href="https://eater.net/6502" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Ben Eater&#x27;s homemade computer</a><sup><a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="#user-content-fn-1">1</a></sup>, so that I could use his toolchain and examples to test my processor.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s some of the assembly I wrote by hand in the early days. This program will capitalize any letter it receives:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">MAPPED_STDIO = </span><span class="" style="color:#8B5CF6">$4001</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">.org</span><span class=""> </span><span class="" style="color:#8B5CF6">$8000</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">reset</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">LDA</span><span class=""> MAPPED_STDIO</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">CMP</span><span class=""> </span><span class="" style="color:#8B5CF6">#$61</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">BMI</span><span class=""> skip_capitalize</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">CMP</span><span class=""> </span><span class="" style="color:#8B5CF6">#$7B</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">BPL</span><span class=""> skip_capitalize</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">AND</span><span class=""> </span><span class="" style="color:#8B5CF6">#$DF</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">skip_capitalize</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">STA</span><span class=""> MAPPED_STDIO</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">JMP</span><span class=""> reset</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">.org</span><span class=""> </span><span class="" style="color:#8B5CF6">$fffa</span><span class=""></span></div><div class="" style="color:#404040"><span class="">vectors</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">.word</span><span class=""> </span><span class="" style="color:#8B5CF6">$0000</span><span class="" style="color:#9CA3AF;font-style:italic">; NMI</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">.word</span><span class=""> reset</span><span class="" style="color:#9CA3AF;font-style:italic">; RESET</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">.word</span><span class=""> </span><span class="" style="color:#8B5CF6">$0000</span><span class="" style="color:#9CA3AF;font-style:italic">; IRQ</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--platform-text"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--platform text</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">How does the above program actually read and write text? This is where <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Platform</code>s come in. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Platform</code> trait provides platform-specific code to run the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">System</code>, and each <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Memory</code> object can keep a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PlatformProvider</code> to provide functionality like writing to the terminal, prompting for input, drawing a pixel on the screen, or checking which keys are pressed.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Text platform is the simplest, only providing read and write capabilities through the terminal.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--system-klaus"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--system klaus</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To verify that every opcode of my 6502 implementation worked, I decided to use <a href="https://github.com/Klaus2m5/6502_65C02_functional_tests" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Klaus Dormann&#x27;s functional tests</a>. This <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">System</code> was the harness that let me run these tests.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--system-easy"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--system easy</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another project I leaned off of was the <a href="https://skilldrick.github.io/easy6502/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Easy6502</a> guide. This guide walks you through writing a game of Snake for a bespoke 6502 system which outputs to a 16x16 color display. I implemented this video system for my emulator, and ran the example implementation of Snake. This basic 16x16 display was substantially more simple than any real-world video circuit, so it made a perfect first implementation.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--platform-winit"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--platform winit</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To actually push pixels to the screen, I landed on using <a href="https://github.com/parasyte/pixels" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">pixels</code></a> to plot pixels and <a href="https://github.com/rust-windowing/winit/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">winit</code></a> to handle creating the window and handling keyboard events. I initially tried using <a href="https://github.com/emoon/rust_minifb" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">minifb</code></a> to handle both tasks, but I found it to have slightly worse performance.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--system-pet"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--system pet</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My next step was to actually implement a real computer. I chose the Commodore PET, since its simple monochrome text-mode graphics would be relatively easy to implement.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the PET, text is placed on the screen by writing a specific character code to a specific location in the video memory. There is no color support, bitmap mode, or other frills -- it&#x27;s just text mode.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The PET also uses a PIA chip, which I needed to implement. This is used to read the keyboard row, and to receive a 60Hz interrupt from the video circuitry.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I did still have to implement the keyboard, which proved slightly difficult. The keyboard layout on the PET&#x27;s &quot;graphics keyboard&quot; (one of the two standard keyboards for the PET) is quite different from a modern computer keyboard. Notably, it places the double-quote <kbd>&quot;</kbd> on a key which does not require <kbd>Shift</kbd> to be pressed. After adding that and a few other special cases, it just required implementing the keyboard matrix scan logic to return the correct bits for each keyboard row.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--target-wasm32-unknown-unknown"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--target wasm32-unknown-unknown</code></h3>
<div class="contents break-inside-avoid print:block"><img alt="A screenshot of a Commodore PET emulator running in a browser." loading="lazy" width="1672" height="1316" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fnoentiendo%2Fwasm.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fnoentiendo%2Fwasm.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fnoentiendo%2Fwasm.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is when I added support for WebAssembly. In a browser, the emulator draws to a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;canvas&gt;</code> element, also using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">winit</code>. (I&#x27;m thinking of transitioning away from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">winit</code> and just directly setting up the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;canvas&gt;</code> through JavaScript bindings.) The Easy6502 implementation works fine, and so does the PET. (The text-mode stuff also works, albeit through <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">alert()</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">input()</code> calls.)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="--system-vic"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--system vic</code></h3>
<div class="contents break-inside-avoid print:block"><img alt="A screenshot of a VIC-20 displaying the BASIC startup screen." loading="lazy" width="928" height="1016" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fnoentiendo%2Fvic.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fnoentiendo%2Fvic.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fnoentiendo%2Fvic.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My most recent work has been trying to emulate the VIC-20. The VIC-20 is named after the <em>VIC chip</em>, or the Video Interface Chip. (Specifically, it&#x27;s the MOS 6560 or 6561 in NTSC and PAL regions respectively.) This chip manages the background and border colors, the sound output, the light pen, and a few other miscellaneous video-related features.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The VIC-20 also trades the PIAs for VIAs. Although the PET contained a VIA, it was only used for the IEEE-488 interface (used for disk/tape drives and storage), so I didn&#x27;t implement it. The VIC-20 uses its VIAs for reading the keyboard state and for setting up a 60Hz timer, both of which are required to get a minimal working system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The VIC-20 uses three separate areas of memory for video-related functions:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The <em>screen memory</em> stores what character is displayed at each position on the screen.</li>
<li class="my-2 pl-2">The <em>character memory</em> stores the shape of each character -- kind of like the &quot;font&quot; of the system.</li>
<li class="my-2 pl-2">The <em>color memory</em> stores the color code for each position on the screen.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>This work is ongoing.</strong> At time of writing, the system boots to the startup screen (with color), but fails to blink the cursor or display typed characters. Work is ongoing in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">bc/vic-20</code> branch of the repo.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-road-ahead">The Road Ahead</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My immediate goal is to get the VIC-20 working, which should happen soon. Past that, and loosely in order of priority, here&#x27;s what I want to tackle:</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="emulating-disk-drives">Emulating Disk Drives</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Currently, the PET can only be used for running programs that you&#x27;re willing to type out at the BASIC interpreter. Emulating a disk drive will make it easier to load a wide array of software, increasing the utility of the emulator and helping to test other parts of the system. Notably, lots of Commodore machines used the same drives, which might make this easy.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cleaning-up-the-webassembly-experience">Cleaning Up the WebAssembly Experience</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, the WebAssembly build is a somewhat manual process built on top of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">wasm-pack</code>, with no automated deployment. Additionally, swapping between systems requires commenting out system-specific code.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;d like the WebAssembly experience to be user-friendly enough for a user to select the system they would like to run from their browser. I&#x27;d also like deployment to be more automated, so pushes to the Git repository will trigger the web deployment to be up to date. This might also be a good time to remove the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">winit</code> dependency for WebAssembly, and to work with the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;canvas&gt;</code> directly. (That would also let us attach event listeners to the page itself, not just the canvas.)</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-commodore-64">The Commodore 64</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;d like to emulate the Commodore 64, due to its popularity. The only substantial difference it has to the VIC-20 is the video circuitry, so once the VIC-20 is working, this should be a pretty easy system to get running.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Notably, the Commodore 64 uses the MOS 6510, not the 6502. This adds an 8-bit I/O port.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="native-mobile-apps">Native Mobile Apps</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While mobile users <em>could</em> use the WebAssembly version, the low performance of mobile devices means that the overhead of WASM makes the experience laggy. A native mobile app could also give a better keyboard experience for users.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-apple-iie">The Apple IIe</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was another popular 6502-based computer with a rich software library. It has less in common with the Commodore machines, meaning it might be more difficult to get working.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="embedded-design-sketching">Embedded Design Sketching</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My vision is to have some physical device with physical controls and a physical display to run the emulator as firmware. I don&#x27;t intend on bringing this to market, partly because I don&#x27;t think there is enough demand and partly because we would have to be careful to avoid copyright issues (e.g. the kernals of the Commodore machines are still protected under copyright). That said, I want it to be inexpensive enough that we could put together a few as a proof-of-concept.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Figuring out the vision for this project requires:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Choosing a chip
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Ideally we&#x27;d want one with good Rust support, like the RP2040</li>
</ul></div>
</li>
<li class="my-2 pl-2">Choosing a display
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">This might be the most expensive part of the system</li>
<li class="my-2 pl-2">We&#x27;d want something with color, and a good &quot;middle ground&quot; aspect ratio</li>
</ul></div>
</li>
<li class="my-2 pl-2">Drawing a schematic</li>
<li class="my-2 pl-2">Laying out a PCB</li>
<li class="my-2 pl-2">Assembly!</li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-nintendo-entertainment-system">The Nintendo Entertainment System</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The NES has a complicated video system with hardware sprites and multiple modes. It will be quite a challenge to implement. It uses the Ricoh 2A03, which is a 6502 clone but doesn&#x27;t have Binary Coded Decimal support for patent-related reasons.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cleaning-up-the-desktop-experience">Cleaning Up the Desktop Experience</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It might be nice to have a user-friendly GUI that allows users to choose their system and ROM before launching. It also might be nice to ship a compiled, signed executable for all platforms.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="additional-systems">Additional Systems</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Other potential 6502-bsed systems include:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><em>Apple I</em>, other members of the <em>Apple II</em> family</li>
<li class="my-2 pl-2"><em>Acorn</em>&#x27;s various Eurocard systems</li>
<li class="my-2 pl-2"><em>Atari</em>&#x27;s 8-bit family including the <em>Atari 400</em> and <em>800</em></li>
</ul></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="support-for-additional-cpus">Support for additional CPUs</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the long term, it might be nice to add support for additional CPUs. Potential candidates include:</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>WDC 65C02, WDC 65C816, Ricoh 5A22:</strong> This family was based on the original 6502. The 65C02 removed some undocumented opcodes, added some new opcodes, and fixed some errata from the old silicon. The 65C816 made even more extensions, including 16-bit registers, but maintains binary compatibility with the 6502. Finally, the Ricoh 5A22 is a clone of the 65C816, similar to how the Ricoh 2A03 clones the 6502.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">WDC 65C02: <em>Apple IIc</em>, <em>Enhanced Apple IIe</em>, <em>BBC Master</em>, <em>Atari Lynx</em></li>
<li class="my-2 pl-2">WDC 65C816: <em>Apple IIGS</em></li>
<li class="my-2 pl-2">Ricoh 5A22: <em>Super Nintendo Entertainment System</em></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>8080, Z80, &quot;GB-Z80&quot;, 8086:</strong> This family of processors was also widely used. The Z80 is an extension of the Intel 8080, and the &quot;GB-Z80&quot; (technically a Sharp LR35902) shares many of the same opcodes. The Intel 8086 has similar opcodes to the 8080.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Intel 8080: <em>Altair 8800</em>, <em>Sol-20</em></li>
<li class="my-2 pl-2">Zilog Z80: <em>ZX Spectrum</em> (and the <em>ZX 80</em> and <em>ZX 81</em>), <em>TRS-80</em>, <em>Cambridge Computer Z88</em></li>
<li class="my-2 pl-2">&quot;GB-Z80&quot; / Sharp LR35902: <em>Game Boy</em>, <em>Game Boy Color</em></li>
<li class="my-2 pl-2">Intel 8086: <em>IBM PC (model 5150)</em>, <em>IBM PS/2</em>, <em>Tandy 1000</em></li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Motorola 68000</strong> This is a 16/32-bit processor with a 32-bit instruction set and a 16-bit data bus. It was used in the <em>Macintosh</em>, <em>Amiga</em>, <em>Atari ST</em>, <em>Sun-1</em>, <em>Apple Lisa</em>, <em>Sinclair QL</em>, and <em>Sega Genesis</em>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="project-name">Project name?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So far, I&#x27;ve just been calling the project &quot;noentiendo,&quot; as a pun on Nintendo and an allusion to the fact that I didn&#x27;t know much about Rust or retro computing before starting this project. I&#x27;ve been thinking about calling it &quot;MoxEMU,&quot; since I really like Moxie soda. I&#x27;d love other suggestions -- maybe one will stick!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="timeline">Timeline</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My immediate priority is getting the VIC-20 working. I don&#x27;t have an ETA on when that&#x27;ll be finish, but my hope is that it&#x27;ll be done by New Years. After that branch gets merged, I&#x27;d love to start working on this with a group of people. Hopefully, once the initial design is frozen, collaboration should be easy due to the modular nature of the system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re interested, get in touch with me, and I can keep you up to date!</p>
<section data-footnotes="true" class="footnotes"><h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="footnote-label">Footnotes</h3>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s also a big part of what inspired me to start this project! <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="#user-content-fnref-1">↩</a></p>
</li>
</ol></div>
</section></div>]]></description>
            <link>https://breq.dev/2022/11/26/noentiendo</link>
            <guid isPermaLink="false">/2022/11/26/noentiendo</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[emulation]]></category>
            <pubDate>Sat, 26 Nov 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[MIDI LiDAR]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/mgYmrErOh_Y/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/mgYmrErOh_Y/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project is a new type of MIDI controller which uses a LiDAR sensor to detect the positions of the user’s hands within two arbitrary zones, then maps this input to four separate MIDI streams. Each stream can send either note values to control pitch or Continuous Controller (CC) messages to control other parameters in a DAW.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This controller uses a single sensor placed in the middle of a playing surface (desk, table, etc). The performer sits or stands such that the sensor is directly in front of their body. On either side of the sensor, zones are marked out in software. These zones can be chosen to align with physical references, such as a printed grid. The performer positions their hands within these zones in the air above the playing surface. Then, the horizontal and vertical position of each hand is used to generate MIDI messages, which control some sound source.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="4000" height="3000" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmidilidar%2Fmidilidar.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmidilidar%2Fmidilidar.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The sensor located on my desk, with two reference sheets corresponding to two zones.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My primary inspiration for this project was the theremin. I had the opportunity to play one when I was young, and I was amazed by how intuitive and inviting the instrument felt. I was quickly able to get a sense for how the pitch and volume were controlled, but I still had the sense that there were so many possibilities for the device despite the simple controls. I was also struck by how inviting the device felt. Generally, when I see an unfamiliar instrument, my first reaction is to hold my hand tentatively over it and ask, “Can I play this?” With the theremin, simply being in the same vicinity affects the sound, and when I curiously held my hand near it, I found I was already playing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Although some highly skilled musicians such as Clara Rockmore developed <a href="https://zwentzen.files.wordpress.com/2010/10/thereminmethod.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">techniques</a> for the theremin that increased its versatility, the instrument is often used simply for “spooky sound effects” instead of pitched music. The <a href="https://www.thomasbloch.net/en_ondes-martenot.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ondes Martenot</a>, a later electronic instrument using a continuous wire, had a non-functional image of a piano keyboard which provided a reference point. This instrument was just as continuous as a theremin, but the reference keyboard made it more widely used in pitched music. It also allowed for multiple configurations of the resonance diffuser, giving the instrument a wider range of timbre.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A more modern inspiration was <a href="https://doi.org/10.21428/92fbeb44.761367fd" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Quadrant</a>, a MIDI controller developed by Chris Chronopoulos from 2018 through 2021. This controller uses upward-facing time-of-flight sensors to determine the pose of the player’s hand. Notably, it features a variety of different control modes: using four sensors, it can detect the position, velocity, orientation, and angular velocity of the performer’s hand. It also features special modes which can detect sweeps of the hand across the instrument, or plucking motions made above each of the four sensors. Of course, being a MIDI controller, it can be used to control an incredible variety of digital sound sources.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was also inspired by the <a href="https://www.laserspectacles.com/resources/the-laser-harp/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">laser harp</a>, another modern MIDI controller played without contact. Again, I appreciated how intuitive the device seemed: by leveraging the existing understanding that people have of traditional harps, the laser harp makes its controls obvious.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With this project, I wanted to create a controller that operated in free space, just like the theremin. I wanted to give a reference to allow for more precise pitch control, like the keyboard provided by the ondes Martenot. I wanted to support various control modes, in a manner similar to Quadrant, and I wanted the device to leverage existing intuitions people have about theremins in the same way the laser harp leverages existing intuitions about harps. Above all, I wanted this controller to be as intuitive and inviting as the theremin felt to me when I was younger.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="lidar-sensor">LiDAR Sensor</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For this project, I am using a LiDAR sensor, specifically the <a href="https://cdn-shop.adafruit.com/product-files/4010/4010_datasheet.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Slamtec RPLIDAR A1</a>. This sensor is used to detect the position of the user’s hands in free space. It features an infrared laser diode and a receiver, mounted on a spinning platform.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This sensor is a type of <a href="https://learn.adafruit.com/slamtec-rplidar-on-pi?view=all" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Time-of-Flight (ToF)</a> sensor. It functions by sending out a pulse of infrared laser light and measuring the time taken for the light to bounce off of an object and return to the receiver. Stationary ToF sensors, like those used on Quadrant, produce a stream of distance values:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">[...]</span></div><div class="" style="color:#404040"><span class="">0.505452 m</span></div><div class="" style="color:#404040"><span class="">0.501343 m</span></div><div class="" style="color:#404040"><span class="">0.499432 m</span></div><div class="" style="color:#404040"><span class="">0.476832 m</span></div><div class="" style="color:#404040"><span class="">[...]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each of these values represents the distance from the sensor to the nearest object it sees at a specific instant in time. The range of these sensors varies: the sensors used on Quadrant had a range of about 1 meter, while this sensor can measure up to 12 meters. As there are no external factors that drastically affect the speed of light, these readings are generally quite accurate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By mounting the ToF sensor onto a rotating platform, it is possible to measure at many different angles and construct a 2D map of the sensor’s surroundings. For this reason, these sensors are often used in robotics, to allow a mobile robot to map its environment, determine its position, and navigate. While LiDAR sensors are used in some autonomous driving applications, they are most commonly used in some high-end robot vacuum cleaners to map and clean a given area. For this MIDI controller, application however, the sensor is kept stationary.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By combining the distance measurements from the laser diode with the angle of the rotating platform at the time of measurement, the sensor constructs a stream of angle/distance pairs.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">[...]</span></div><div class="" style="color:#404040"><span class="">30.12° 0.505452 m</span></div><div class="" style="color:#404040"><span class="">31.10° 0.501343 m</span></div><div class="" style="color:#404040"><span class="">32.07° 0.499432 m</span></div><div class="" style="color:#404040"><span class="">32.98° 0.476832 m</span></div><div class="" style="color:#404040"><span class="">[...]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The sensor used for this project produces a stream of about 1,500 such pairs every second and sends this data to the computer over a serial port.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sensor-controlprocessing-program">Sensor Control/Processing Program</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The sensor data is then passed to the first of two computer programs I wrote for this project. This first program is responsible for processing the sensor data and determining the position of each hand within each zone. If the sensor is rotating quickly enough, we can consider every measurement taken in one rotation as a single snapshot in time. Then, for each angle/distance pair, we can plot a point on a graph at the given angle and distance from the origin. In other words, we convert the pairs of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mtext>angle</mtext><mo separator="true">,</mo><mtext>distance</mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(\text{angle},\text{distance})</annotation></semantics></math></span></span> coordinates to pairs of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y)</annotation></semantics></math></span></span> coordinates and display the result.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="752" height="620" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmidilidar%2Fsensor_raw.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmidilidar%2Fsensor_raw.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmidilidar%2Fsensor_raw.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Pictured above is the graph that is generated from this process. In this image, the green dots represent points found by the laser, and the purple and orange rings mark distances of 50 and 100 centimeters away from the sensor respectively. This scan was performed in my dorm room. This complete plot of the environment includes the performer’s body, the walls of the room, and any other objects in the environment at the sensor’s height. Next, it is necessary to filter out anything from the environment which should not affect the output, i.e., everything except the hands of the performer. I decided to accomplish this by processing two separate zones in the pot, such that the performer could move each of their hands within its respective zone in two dimensions. This results in four separate axes of control. The use of two separate zones was chosen to maximize the number of axes available to the user, and to eliminate the possibility of one hand obscuring the other.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="752" height="620" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmidilidar%2Fsensor.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmidilidar%2Fsensor.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmidilidar%2Fsensor.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The user can select these two zones by clicking each of the eight corners on the graph. In the chart above, my left hand is visible in the zone on the left side and my right hand is visible in the zone on the right side. The sensor, represented by the white dot in the center, is located in the middle of the playing surface, between the zones and directly in front of my body. The software will calculate the position of each point in the scene along each of the four axes. To do this, it constructs a projective transformation matrix, which is a technique from linear algebra. The following procedure was adapted from this <a href="https://math.stackexchange.com/a/339033" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">StackExchange answer</a> by Dr. Martin von Gagern.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The software knows the coordinates of each corner in space, and it knows the position of each corner relative to each axis (the point where the two axes meet is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mn>0</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(0,0)</annotation></semantics></math></span></span>, the opposite corner is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mn>1</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(1,1)</annotation></semantics></math></span></span>, etc). Using this, it will construct a transformation matrix that can map any point in space to its position relative to each axis. Intuitively, the most obvious approach would be to construct a <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn><mo>×</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">2\times2</annotation></semantics></math></span></span> matrix to perform this mapping, since we are working with pairs of coordinates. However, since we have four separate known mappings, the matrix would have too many conditions to meet. The system would be overdetermined and thus impossible to solve.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can add “wiggle room” to the system by using a <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">3\times3</annotation></semantics></math></span></span> matrix instead. However, this requires representing our coordinates using triples instead of pairs. In other words, this requires some way of representing coordinates in the form <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo separator="true">,</mo><mi>z</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y,z)</annotation></semantics></math></span></span> instead of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y)</annotation></semantics></math></span></span>. One such approach uses homogenous coordinates. Mapping Cartesian coordinates to homogenous coordinates requires adding a 1 in the third position (such that <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y)</annotation></semantics></math></span></span> becomes <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y,1)</annotation></semantics></math></span></span>), and mapping homogenous coordinates back into Cartesian coordinates requires dividing the first and second coordinates by the third coordinate (such that <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo separator="true">,</mo><mi>z</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y,z)</annotation></semantics></math></span></span> becomes <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mfrac><mi>x</mi><mi>z</mi></mfrac><mo separator="true">,</mo><mfrac><mi>y</mi><mi>z</mi></mfrac><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(\frac x z, \frac y z )</annotation></semantics></math></span></span>). Notably, this system creates a set of points with homogenous coordinates of the form <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>y</mi><mo separator="true">,</mo><mn>0</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(x,y,0)</annotation></semantics></math></span></span> which cannot be mapped to Cartesian coordinates due to the division by zero. Conceptually, these points represent scenarios like the following:</p>
<img class="mx-auto" src="/images/diagrams/homogenous-coordinates.svg" alt="" width="494" height="181"/>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The above point is around <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2.5</mn></mrow><annotation encoding="application/x-tex">2.5</annotation></semantics></math></span></span> on Axis 0, but it has no meaningful coordinate on Axis 1. As the positions of the performer’s hands should be inside the quadrilateral zone, scenarios like the above should never arise in this application. For each zone, the software will map the four corner points to their homogenous representation, then compute the <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3</mn><mo>×</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">3\times3</annotation></semantics></math></span></span> transformation matrix. Then, it will sort the points based on whether the lie inside the zone. It determines if each point is inside the zone by checking if its position on either axis is less than zero or greater than one; although more optimized methods of testing if a point is inside a quadrilateral certainly exist, I found the matrix multiplication to be approach reasonably performant, so I decided against introducing additional complexity. Finally, to create a single value for each axis, the software will average the axis positions of each point located inside the zone. This step produces a stream of four numbers, representing the position of both hands along both axes in their respective zones:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">[...]</span></div><div class="" style="color:#404040"><span class="">0.43 0.53 0.21 0.54</span></div><div class="" style="color:#404040"><span class="">0.41 0.57 0.19 0.50</span></div><div class="" style="color:#404040"><span class="">0.42 0.61 0.19 0.52</span></div><div class="" style="color:#404040"><span class="">[...]</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This stream of values is then passed to the second program.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="midi-mapping-program">MIDI Mapping Program</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The second program handles mapping each axis to a stream of MIDI data. Each of the four axes are handled separately. The program provides a control panel which allows configuring the parameters of the MIDI stream.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="328" height="900" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmidilidar%2Fmapping.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2Fimages%2Fmidilidar%2Fmapping.png&amp;w=750&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fmidilidar%2Fmapping.png&amp;w=750&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each axis can be mapped to either note messages or Continuous Controller (CC) messages. For note messages, the range of notes is configurable. Additionally, pitch bend messages can be sent to smoothly interpolate between notes. As currently implemented, transitioning from one note to another smoothly requires that the sound source envelope not have attack/decay or this attack will be audible when moving between notes. For CC messages, the control number can be chosen. Both modes support setting the channel number, which is useful for having different axes control different synth voices. A setting to invert the signal is also provided.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="surface-configurations">Surface Configurations</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since the scanning occurs about 2 inches above the tabletop, the user does not need to touch any surface to use the controller. This allows any variety of surfaces to be placed underneath the setup. The simplest configuration has no reference material whatsoever. However, I found that this made it difficult to use, as I would frequently move my hand into or out of the zone without knowing, unexpectedly playing or stopping a tone.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also tried placing a piece of paper in each zone and calibrating the corners of the zone to the corners of the page. This solved the aforementioned issue. This configuration worked well for controlling a filter or some other aspect of the sound timbre, but I found that an 8.5x11” zone was too small to precisely control pitch. I also found that, without any sort of reference, it was almost impossible for me to precisely reach a specific note. At this time, I tried decreasing the range of the controller from 2 octaves to 1 octave in an attempt to improve precision, without much success. I also tried disabling the pitch bend messages to see if snapping the hand position to the nearest note would help, but I did not notice any improvement in usability.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, I tried an asymmetrical layout, with a portrait-oriented paper on the left and two landscape-oriented pages arranged lengthwise on the right. The left page showed a 4x4 grid, and the right pages had vertical lines denoting the boundaries between notes. I found that this made me much more able to hit a specific note within the octave range, even if I re-enabled pitch bends. That said, I did not play along to any other instruments, so I might have overestimated my ability to precisely reach a particular pitch.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I found it difficult not to hit intermediate notes. While moving my hand, even quickly, if a scan saw my hand in the middle of the move, it would play the intermediate note for about 150ms, which sounded jarring at times. To compensate, I played by lifting my hand up away from the zone before moving it side-to-side between notes. This created an additional issue: due to the shape of my hand, moving it up and down could alter the detected position along the forward-and-back axis. I tried playing using a credit card instead of my hand but found it just as easy to ensure my hand was not tilted forward or back when moving it up or down.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="mapping-configurations">Mapping Configurations</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first mapping I tried used the controller to control a wavetable synthesizer in Ableton Live. It used the right hand horizontal axis to control pitch and the right hand vertical axis to control the position on the wavetable. The left hand controlled a low-pass filter, with the horizontal axis controlling the frequency cutoff and the vertical axis controlling the resonance parameter. I found wavetable synthesis to be a natural fit for this controller, since having a single parameter as the primary control of the sound’s timbre could give one hand control over both pitch and timbre, freeing up the other hand for controlling an effect or a second sound source.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My attempt at controlling two synthesizer voices with this project used one hand for each voice, making use of my software’s ability to send MIDI note messages on different channels corresponding to the input axis. I again used the horizontal axis for pitch. One patch was a sawtooth wave bass, with the vertical axis controlling a low-pass filter, and the other patch was a square wave in a higher octave, with the vertical axis controlling a delay effect. I found controlling two separate voices to be overwhelming. This was mostly because I needed to visually look at each hand to position it properly, which was difficult as they were on opposite sides of the playing area. I also found the delay effect control to have little utility outside of “spooky sound effects.” I did enjoy “tuning” the two voices to an interval by ear (since I was not playing with a note reference at the time) and controlling an almost-exact-interval with my hands to slightly tune or detune it produced some interesting sounds, but this was made difficult by the latency issues (discussed in a later section).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Returning to the wavetable and low-pass filter mapping, I tried an alternate playing style by placing a small object in the filter zone to hold those axes at a specific point. This allowed me to focus more on playing melodies with my right hand and less on holding my left in a specific stable position. However, it also removed the “free space” aspect of the controller for that zone, and it took away from the direct coupling of hand-to-sound, reintroducing that hesitance before manipulating a control. Ultimately, this playing style is a tradeoff, diverging from my original vision in pursuit of usability, and I believe having it as an option is a net benefit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I tried using the controller in conjunction with a traditional MIDI keyboard. I deactivated the right side zone, placed the keyboard in its place, and kept my left hand in the left side zone. I found that this combination worked surprisingly well: the traditional keyboard allowed me to play notes accurately and with minimal latency, while my left hand was free to alter the timbre of the sound. I found the LiDAR controller felt much more expressive than a simple mod wheel; even though it had similar precision, having a large physical representation of a parameter gave me a better sense of the possibilities it provided, and coupling my hand directly to its output made experimentation feel more direct and natural. While this configuration also does not follow my initial vision for a single, configurable, all-purpose MIDI controller, it succeeded in allowing for intuitive and inviting free-space control of timbre while falling back on a familiar interface for pitch.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Across each of these configurations, I found that mapping two related parameters to a single hand fundamentally changed how I conceptualized the controls. I tried a few mappings of this sort, such as controlling the frequency and resonance of a filter, the time and feedback of a delay module, the amount and wet/dry mix of an overdrive effect, and the rate and feedback of a phaser. In each of these cases, I found I had a much better sense of the timbre possibilities offered by these controls. With a traditional controller, with single knobs mapped to single controls, I would typically move just one knob at a time and miss vast swaths of potential sounds. Having two parameters mapped to 2D space encourages moving diagonally or in a circle to modify both parameters at once. Even though traditional CC controllers allow mapping the same knob to several controls, it is not the typical use case.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of my favorite moments was finding a particular spot in the 2D zone that caused the filter to resonate particularly strongly or the phaser to fit the melody I was playing perfectly. Exploring these pockets gave me the sense that I was exploring something inherently two-dimensional, instead of just manipulating two parameters at once.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="scan-latency">Scan Latency</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The issue of scan latency proved difficult to overcome. The LiDAR sensor that I use spins at about 380 RPM when connected to 5V power, leading to an effective latency of around 150 milliseconds between scans. This had a few adverse effects on the usability of the controller.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first issue with the scan latency was the creation of a 150ms “rhythm” in the audio output. The output would jump sharply to a new pitch whenever scan data was received, so if the user were to attempt a gradual slide between notes, the result would sound much choppier than intended. A potential remedy is to interpolate smoothly between scans, ramping to each value just as the measurement following it is received, but doing this would double the effective latency of the controller. This behavior could also perhaps be used for effect if a piece were specifically written around it, but the sensor used in this project does not provide an easy way to precisely control the speed of the scan motor, so synchronizing it to a track would be difficult. Given the scope of the project, I decided against trying to implement precise motor speed control, although this could be a direction for further experimentation.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The latency also proved to be a barrier for precise control. I had assumed that 150ms, while by no means ideal, would be passable for syncing with the beat of slower music, but I overlooked the impact of latency on other aspects of performance. With a continuous instrument (such as a theremin, or playing a guitar with a slide), the performer will adjust in real-time using their ear until they are playing the correct pitch. With the LiDAR controller, the presence of latency disrupts this real-time adjustment, making it extremely difficult to play in tune. Removing the pitch bend messages and snapping each position to the nearest semitone helped, but it was still difficult to precisely position one’s hand in a specific 1/12 of the zone without any markings for guidance. Ultimately, a visual reference was required to accurately play pitches, and even then, the controller was difficult to use.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="future-directions">Future Directions</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One direction I did not explore was integrating graphic scores into the project. While I tried a configuration with a general note reference on the surface, it would also be possible to use a reference designed for a specific piece of music. Such a reference would be, in a sense, musical notation for the piece. A potential difficulty with this is the lack of a “time” axis to draw on, which could make duration or progression difficult to express in this notation.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another potential direction is to use interpolation to reduce the jarring steps between scans. I intended to implement this, but the way in which I structured my software did not lend itself well to this approach, and a major restructuring would be needed. I am curious as to how adding glide between scans would affect the perceived latency of the controller, and how much it would improve the discontinuity between scans. Notably, different amounts of glide could be explored. For example, interpolating for the full 150ms would yield the smoothest output but the greatest latency, but perhaps a glide of 20ms could mitigate most of the jarring clicks/jumps in the audio with a minimal impact on latency. Additionally, different interpolation functions could be explored.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">More direct control over the sensor itself could be explored. The specific model of LiDAR sensor I used lacks an interface to control the motor speed or scan rate. A sensor with more features could allow tweaking these parameters, potentially leading to reduced latency and/or increased precision, especially since LiDAR sensors are typically calibrated to scan an entire room instead of a tabletop surface. The scan rate could even be tuned to match the tempo of the music being played, resulting in a much more predictable playing experience and eliminating the issue of sudden off-beat jumps in output.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, gathering the perspectives of others would be an important next step. Due to the timing of this project ending over spring break, I did not have much opportunity to share this project with others. Of course, when I tested the controller, I brought along my past intuitions and experiences. Other users would be drawing on different prior intuition when using the controller, and understanding its usability from more perspectives could inform how it could be made more intuitive and more inviting.</p></div>]]></description>
            <link>https://breq.dev/projects/midilidar</link>
            <guid isPermaLink="false">/projects/midilidar</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[MOTD Necklace]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="6904" height="4608" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmotd%2Fboth.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmotd%2Fboth.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The necklace, in two separate frames. On the left, it shows my radio license in a heart-shaped frame, on the right, it shows a message my friend came up with in a square frame.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project is a necklace/amulet which can display a message on an e-Ink display. The persistence of the e-Ink makes it so that the display, once updated, does not require a battery to hold the image.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was inspired by a display pendant idea I found on <a href="https://learn.adafruit.com/trinket-slash-gemma-space-invader-pendant?view=all" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit</a> which used an ATTiny85 and an LED matrix display to show an animation. I wanted to experiment with a wearable project, but I didn&#x27;t want to have to remember to charge it or design it around a large battery.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was also looking for an excuse to use an e-Ink display for a project, due to their unique design considerations and low power requirements. I decided an e-Ink screen could be a good fit for this project.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The project consists of a monochrome <a href="https://www.adafruit.com/product/4196" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">e-Ink display</a> mounted in a 3D printed frame. Pogo pins on a programming board I made are used to connect to the pins on the display breakout board.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3000" height="4000" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fmotd%2Fboard.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fmotd%2Fboard.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The programming board, featuring an M0 board and a row of pogo pins.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve printed two frames that I can switch between: a smaller, square one and a larger, heart-shaped one.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Even though I didn&#x27;t put any special attention into weatherproofing, I&#x27;ve worn the necklace outdoors / in the rain / on the beach plenty and I haven&#x27;t had any issues. This is probably because there is no onboard power: even though there is no special ingress protection, the display is fine as long as it is dry when programmed. Also, the 3D printed frame seems to fit the display tightly enough that dust/dirt/sand exposure doesn&#x27;t pose much of an issue.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The programming process ended up working pretty well, and it&#x27;s definitely something I could change every day if I had the motivation to. The pogo pins do require force to push the display against them, and slipping up at the wrong moment can cause the display to be refreshed improperly, causing issues. Some sort of latch to hold the display in place during programming could have remedied this.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Overall, I&#x27;m happy with how this project turned out, and the necklace (with clever message) has become a common feature of my daily outfit.</p></div>]]></description>
            <link>https://breq.dev/projects/motd</link>
            <guid isPermaLink="false">/projects/motd</guid>
            <category><![CDATA[arduino]]></category>
            <category><![CDATA[c++]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Artificial Soundscapes]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1481" height="1057" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fsoundscapes.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fsoundscapes.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fsoundscapes.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A screenshot of the sonification project in Ableton Live, with data mapped to automation lanes and MIDI notes.</p>
<div></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The final sonification, featuring sounds generated from Boston, Los Angeles, and Anchorage data.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this project, I attempted to use data sonification techniques to create abstract soundscapes for various cities. Notably, the process of producing sounds from data is as similar as possible between the various cities, to allow the listener to compare and contrast these cities based on each soundscape. I wanted the listener to be able to understand the different aesthetic of each soundscape as a whole, but I also wanted to enable the listener to recognize how specific measurements and data points differed both over time and between cities.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One challenge I had was balancing the aesthetics of the compositions with the need to convey information. Sonification typically uses more abstract sound sources, but these can sound jarring and disjointed. Alternatively, soundscapes often blend layers of sound together, but this using this approach could impede the actual presentation of data. As I wanted to create a piece which could serve both purposes, I needed to find a compromise between these opposing goals.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To solve this, I decided to focus solely on facilitating comparing and contrasting of data. Thus, I could tweak the processing of data to achieve a more pleasant sound, and as long as I applied these tweaks equally to each city, it would not impact the listener’s ability to compare between cities. An example of this sort of tweak is mapping data to notes of the major scale instead of arbitrary pitches: this removes distracting dissonance and yields a more musical result, but it does further decouple the audio result from the data it is based on.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I drew some inspiration from soundscape compositions, which framed naturally occurring sounds in a more narrative way. Instead of sounds recorded from nature with a microphone, however, I worked with sounds generated from natural data. I focused on natural data partly as an homage to soundscape compositions. I only used data from the span of one year, but a future project could examine multiple decades to show a more dynamic view of the climate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of the primary criticisms of sonification as a field is bias in the data processing: when creating an algorithm to convert data into sound, people are likely to have some preconceived notion of what the result should sound like. (For instance, sonification of space-themed data often uses whooshing noises, not because they aid in interpreting the data, but because science fiction has taught us that is what space should sound like.) The mapping from data to sound should leverage existing intuition if possible to help listeners understand the audio, but it should not detract from the data itself. These decisions have to be arbitrary, making this a difficult problem to solve. I drew heavily on Tantacrul&#x27;s <a href="https://www.youtube.com/watch?v=Ocq3NeudsVk" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">critique</a> of sonification during this project to understand this balance.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of the most important facets of this project is that the same processing is applied to each city. My hope is that this reduces the likelihood that the processing step I have developed is biased towards telling a specific story or focusing on a particular theme. Without this restriction in place, I might allow my own understanding of cities to influence the results of the sonification instead of letting it be driven by the data.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I sourced weather data for this project from NOAA’s free Climate Data Online service. This service gives daily temperature, wind, and precipitation data for a large number of stations both in the US and globally. Most US cities had multiple stations to choose from, and I specifically chose stations from the largest nearby airport (as these stations often report more data). I chose to analyze data over the course of one year, since it was a short enough interval for individual days to be represented as notes while being long enough to demonstrate the periodic nature of the seasons. I looped this data to emphasize this repetition. I also sourced tidal data from NOAA’s Tides and Currents service.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While I wanted to try to incorporate additional sources (such as public transit data, air traffic data, or highway traffic data) into the soundscape, I could not find a practical way to accomplish this. Although many cities have transit APIs, these lack consistency, and they generally only provide data about the current location of trains and the predicted future schedule (whereas this project is more focused on historical data). Most air traffic APIs are similarly focused on present and future data only, and their rate limits would not allow me to retrieve a year’s worth of data at once for this project. Finally, highway traffic is generally not precisely measured on a real-time basis.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To process this data, I first used a Jupyter notebook and Python code. This program would read the data from each source, then map each data point to one or more MIDI messages. These messages were organized first by city, then by data source, then by “tick” / instant in time. Messages could be grouped together to occur at the same instant, and a list of “cleanup” messages was kept ensuring no notes were left playing if the audio was stopped early. I arbitrarily chose that a tick would represent 1/20 of a second, as it was fast enough for the data to sound continuous but slow enough for the ear to pick out individual data points.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">CITIES </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;LAX&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;ANC&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;BOS&quot;</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">@dataclass</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">class</span><span class=""> </span><span class="" style="color:#404040">Tick</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    messages</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">list</span><span class="" style="color:#ff218c">[</span><span class="">mido</span><span class="" style="color:#ff218c">.</span><span class="">Message</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    cleanup</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">list</span><span class="" style="color:#ff218c">[</span><span class="">mido</span><span class="" style="color:#ff218c">.</span><span class="">Message</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">TRACKS</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">dict</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">str</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">dict</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">str</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">list</span><span class="" style="color:#ff218c">[</span><span class="">Tick</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    city</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#1bb3ff">&quot;weather&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#1bb3ff">&quot;tides&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">for</span><span class=""> city </span><span class="" style="color:#8B5CF6">in</span><span class=""> CITIES</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Additional Python code could then play back these tracks simultaneously over different MIDI channels. An Ableton Live set was configured to receive MIDI input, send separate channels to separate tracks with separate instruments, and map MIDI CC messages to instrument parameters to allow them to be controlled by the incoming data. Finally, the resulting audio was recorded using Ableton Live. I chose this workflow because I had familiarity with these tools, having used Ableton Live for production and Jupyter notebooks for analysis (albeit not simultaneously). Although I experimented with using Max/MSP for a potentially cleaner and more extensible implementation, the timeline for the project made a familiar workflow more pragmatic.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to select cities with different climates in order to highlight their differences. I started with Boston, as I have familiarity with its climate. I considered New York, but I decided against it due to its proximity to Boston and similar weather. Next, I chose Los Angeles due to its famously stable climate, followed by Anchorage for its unique geographic location.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While creating the sonification, I noticed that it was difficult to effectively use pitch to represent multiple measurements. Generally, pitch is an appealing quantity to map to, since it has such a wide range and is easy for the ear to recognize. However, mapping multiple quantities to different pitches simultaneously raised some problems. When multiple pitches are being played simultaneously, the listener can end up focusing on the interval instead of each separate note. This detracts from the interpretation of data since whether the interval is major or minor typically does not represent anything. This issue can be remedied somewhat by using distinct sound sources or by playing in separate registers, but I still decided to only map one measurement to pitch.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Another tradeoff I made related to the continuous nature of the phenomena being sonified and the discrete nature of the measurements taken. I found that directly mapping measurements to synthesizer controls could produce a result which sounded discontinuous and disjointed, and I considered implementing some form of interpolation to make the audio result sound smoother. Ultimately, I decided against this, as it could potentially lead to a misleading presentation of the data. This decision, however, did make the audio result less pleasant to listen to, and it compromised on the soundscape aesthetic which I was trying to achieve.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After making these tradeoffs, I eventually decided that trying to create a meaningful and useful sonification which was also an artistic piece following the aesthetic of a soundscape would not be a feasible endeavor. <a href="https://sonification.de/son/definition/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">The aim of sonification is to present data in a scientific sense for the ear to recognize new patterns, not to create a piece of music.</a> Massaging the data and representation to fit a specific predetermined aesthetic is fundamentally at odds with this goal. To be true to the sonification of data, I decided to focus less on the soundscape aspect of the project.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This also highlighted a flaw in my earlier assumption that the comparative nature of this project could help eliminate bias. Although the sonification would not be biased based on one specific city’s impact on popular culture, it could still be biased based on my preconceptions about what aesthetic cities should have, the cultural significance of the weather, and other factors unrelated to the data itself. Additionally, through chasing a particular aesthetic, the resulting sonification could lose information, making it less effective at triggering new insights.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to use two sound sources: a wavetable synthesizer to represent the weather data, and white noise to represent the tidal data. The pitch of the wavetable was controlled by the maximum temperature on a given day. I used the Ableton “Basic Shapes” preset and mapped the wavetable parameter to the wind speed such that calm days were represented by sine or triangle waves and windy days were represented by sawtooth waves. Finally, I used a low-pass filter on the white noise controlled by the water level to represent the tides.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I used linear equations to map specific data values to notes or parameter values, attempting to show as much of the usable range as possible. However, while tuning these equations, I focused most on the Boston data. When I tried these equations with the Anchorage data, I found that the temperature dipped below the valid range of note values and the tide data was typically beyond the possible extremes of the control. The tide data was still understandable, but the temperature graph was inaudible during winter.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With a visual graph, I would simply make the graph taller to include all of the relevant information. With sonification, however, I have to work within a limited range of notes. I considered adjusting the mapping to raise all notes up, but that could have caused issues with representing the Los Angeles summer (or any warmer cities, for that matter). I also considered making the mapping denser to fit both low and high temperatures, but that would have made the Los Angeles data harder to understand (as there was little variation from winter to summer anyway, and I wanted to not flatten this any further). I also could have shifted just the Anchorage data up. While that could have helped the listener understand more about the climate of Anchorage, it would have interfered with their ability to compare the data with the other cities. Eventually, I decided to leave the equations in place, allowing some of the Anchorage data to be missing from the audio result.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Overall, the sonification I produced has some utility, although it did not accomplish the goals I started with. While developing the data processing pipeline, I found that I needed to let go of my expectations for the aesthetic of the result if I wanted the data to be presented in a meaningful way. I also found it more difficult than expected to develop a mapping from data to sound which leveraged the existing intuition of the listener without being biased towards a particular narrative and overshadowing the data. Listening to the end result, I can certainly hear and recognize specific differences between the cities: the stable climate of Los Angeles, the harsh winds of Boston, and the extreme tides of Anchorage.</p></div>]]></description>
            <link>https://breq.dev/projects/soundscapes</link>
            <guid isPermaLink="false">/projects/soundscapes</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Wordle Clones]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="425" height="310" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwordle%2Ftypescript.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwordle%2Ftypescript.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwordle%2Ftypescript.png&amp;w=1080&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://en.wikipedia.org/wiki/Wordle" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Wordle</a> is a popular word guessing game, kind of like Mastermind but for letters. I&#x27;ve written a few clones of the game in different languages.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The TypeScript clone was born out of a desire to understand the letter coloring procedure. I had played Wordle before, and I wanted to see if I could implement it myself.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Rust clone was written because I wanted to learn more about Rust. I figured Wordle was a complex enough game that implementing it cleanly would require a decent understanding of Rust features and best practices.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="typescript">TypeScript</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="309" height="423" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwordle%2Ftypescript_complete.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwordle%2Ftypescript_complete.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwordle%2Ftypescript_complete.png&amp;w=640&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Code is available at <a href="https://github.com/breqdev/wordle" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">breqdev/wordle</a>.</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I built this project in React, but I wanted to ensure the game logic was sufficiently decoupled from the rendered result. I wrote this logic in two pure TypeScript functions, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rowColoring</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">keyboardColoring</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The row coloring function assigns colors to each letter in the word. I took advantage of the type system to define letters in the target as explicitly nullable, allowing them to be &quot;removed&quot; when matched by a letter in the guess.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">export</span><span class=""> </span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">rowColoring</span><span class="" style="color:#ff218c">(</span><span class="">guess</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">string</span><span class="" style="color:#ff218c">,</span><span class=""> target</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">string</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Store the color alongside each guess letter</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> guessLetters</span><span class="" style="color:#8B5CF6">:</span><span class=""> LetterGuess</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> guess</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">split</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">letter</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    letter</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    color</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Store the target in an array of nullables</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> targetLetters</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">string</span><span class=""> </span><span class="" style="color:#8B5CF6">|</span><span class=""> </span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">...</span><span class="">target</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// First pass: match green letters</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  guessLetters </span><span class="" style="color:#8B5CF6">=</span><span class=""> guessLetters</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> index</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// green letters are matched by the specific index in the target</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">letter </span><span class="" style="color:#8B5CF6">===</span><span class=""> targetLetters</span><span class="" style="color:#ff218c">[</span><span class="">index</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// remove matching green letters from the pool</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// so that they aren&#x27;t also matched as yellows</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      targetLetters</span><span class="" style="color:#ff218c">[</span><span class="">index</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;green&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Second pass: greedily match yellow letters</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  guessLetters </span><span class="" style="color:#8B5CF6">=</span><span class=""> guessLetters</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;green&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// don&#x27;t modify existing green letters</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// yellow letters are matched by searching the entire target word</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> targetLetters</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">includes</span><span class="" style="color:#ff218c">(</span><span class="">letter</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// remove yellow letters once matched,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// each letter only matches once</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      targetLetters</span><span class="" style="color:#ff218c">[</span><span class="">targetLetters</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">indexOf</span><span class="" style="color:#ff218c">(</span><span class="">letter</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;yellow&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> guessLetters</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In Wordle, the keyboard serves an important role: it shows how much information you have gotten about a letter based on your guesses. Dark gray signifies that the letter does not appear (it was not colored in a previous attempt), yellow signifies that it does appear (it was colored yellow in a previous attempt), and green signifies that you have correctly guessed the position at least once (it was colored green in a previous attempt). As the coloring of each letter relies on the coloring of previous attempts, the keyboard coloring function makes use of the row coloring function to color each of the guesses.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">export</span><span class=""> </span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">keyboardColoring</span><span class="" style="color:#ff218c">(</span><span class="">guesses</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">string</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""> target</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">string</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> letters</span><span class="" style="color:#8B5CF6">:</span><span class=""> Record</span><span class="" style="color:#8B5CF6">&lt;</span><span class="" style="color:#1bb3ff">string</span><span class="" style="color:#ff218c">,</span><span class=""> LetterGuess</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">const</span><span class=""> letter </span><span class="" style="color:#8B5CF6">of</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abcdefghijklmnopqrstuvwxyz&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    letters</span><span class="" style="color:#ff218c">[</span><span class="">letter</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">const</span><span class=""> guess </span><span class="" style="color:#8B5CF6">of</span><span class=""> guesses</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> coloring </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">rowColoring</span><span class="" style="color:#ff218c">(</span><span class="">guess</span><span class="" style="color:#ff218c">,</span><span class=""> target</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> letter</span><span class="" style="color:#ff218c">,</span><span class=""> color </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">of</span><span class=""> coloring</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">letters</span><span class="" style="color:#ff218c">[</span><span class="">letter</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        letters</span><span class="" style="color:#ff218c">[</span><span class="">letter</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;black&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">letters</span><span class="" style="color:#ff218c">[</span><span class="">letter</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;gray&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">||</span><span class=""> color </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;green&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        letters</span><span class="" style="color:#ff218c">[</span><span class="">letter</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">=</span><span class=""> color</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> letters</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rust">Rust</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Code is available at <a href="https://github.com/breqdev/rust_wordle" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">breqdev/rust_wordle</a>.</em></p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="697" height="553" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwordle%2Frust.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwordle%2Frust.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwordle%2Frust.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to rely on as many zero-cost abstractions as possible. For storing each row and each word, instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Vec</code>s allocated on the heap, I decided to use fixed-length arrays with type aliases:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">type</span><span class=""> </span><span class="" style="color:#404040">Word</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">char</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">5</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#1bb3ff;font-style:italic">#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">struct</span><span class=""> </span><span class="" style="color:#404040">Square</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  color</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  letter</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">char</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">type</span><span class=""> </span><span class="" style="color:#404040">Row</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#404040">Square</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">5</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I used a Trait to implement printing the row:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">trait</span><span class=""> </span><span class="" style="color:#404040">PrintWordle</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">fn</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">print_wordle</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">self</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">impl</span><span class=""> </span><span class="" style="color:#404040">PrintWordle</span><span class=""> </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#404040">Row</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">fn</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">print_wordle</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">self</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">for</span><span class=""> square </span><span class="" style="color:#8B5CF6">in</span><span class=""> </span><span class="" style="color:#8B5CF6">self</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">iter</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#8B5CF6">mut</span><span class=""> boxed </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;│ &quot;</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">to_owned</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      boxed</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">push_str</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">&amp;</span><span class="">square</span><span class="" style="color:#ff218c">.</span><span class="">letter</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">to_string</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      boxed</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">push_str</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot; │&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">print_colored</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">&amp;</span><span class="">square</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">boxed</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">print!</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;  &quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">println!</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  row</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">print_wordle</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tried to make use of a functional style for the scoring algorithm, relying on iterators for most of the heavy lifting:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">fn</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">score_guess</span><span class="" style="color:#ff218c">(</span><span class="">target</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#404040">Word</span><span class="" style="color:#ff218c">,</span><span class=""> guess</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#404040">Word</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">-&gt;</span><span class=""> </span><span class="" style="color:#404040">Row</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Map each letter of the target to an Option, so we can &quot;remove&quot; it later</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#8B5CF6">mut</span><span class=""> remaining </span><span class="" style="color:#8B5CF6">=</span><span class=""> target</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">|</span><span class="">c</span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#404040">Some</span><span class="" style="color:#ff218c">(</span><span class="">c</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// All tiles start off white</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#8B5CF6">mut</span><span class=""> result </span><span class="" style="color:#8B5CF6">=</span><span class=""> guess</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">|</span><span class="">letter</span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#404040">Square</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    color</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">White</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    letter</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Use `.enumerate()` to check for the right tile in the right index</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">i</span><span class="" style="color:#ff218c">,</span><span class=""> square</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">in</span><span class=""> result</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">iter_mut</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">enumerate</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> target</span><span class="" style="color:#ff218c">[</span><span class="">i</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">==</span><span class=""> guess</span><span class="" style="color:#ff218c">[</span><span class="">i</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      square</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">Green</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      remaining</span><span class="" style="color:#ff218c">[</span><span class="">i</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">None</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Greedily take remaining unmatched target letters to turn guess letters yellow</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">i</span><span class="" style="color:#ff218c">,</span><span class=""> square</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">in</span><span class=""> result</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">iter_mut</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">enumerate</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> square</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">White</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#8B5CF6">let</span><span class=""> </span><span class="" style="color:#404040">Some</span><span class="" style="color:#ff218c">(</span><span class="">pos</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> remaining</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">iter</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">position</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">|</span><span class="" style="color:#8B5CF6">&amp;</span><span class="">c</span><span class="" style="color:#ff218c">|</span><span class=""> c </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#404040">Some</span><span class="" style="color:#ff218c">(</span><span class="">guess</span><span class="" style="color:#ff218c">[</span><span class="">i</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        square</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">Yellow</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        remaining</span><span class="" style="color:#ff218c">[</span><span class="">pos</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">None</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Any unmatched squares become gray</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">for</span><span class=""> square </span><span class="" style="color:#8B5CF6">in</span><span class=""> result</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">iter_mut</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> square</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">White</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      square</span><span class="" style="color:#ff218c">.</span><span class="">color </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">Gray</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  result</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tried to keep this all straightforward, but I still wasn&#x27;t too confident that I had nailed all of the edge cases. I was delighted by Rust&#x27;s testing support:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic">#[cfg(test)]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">mod</span><span class=""> </span><span class="" style="color:#8B5CF6">tests</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">use</span><span class=""> </span><span class="" style="color:#8B5CF6">super</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#8B5CF6">*</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">fn</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">expect_score</span><span class="" style="color:#ff218c">(</span><span class="">target</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">str</span><span class="" style="color:#ff218c">,</span><span class=""> guess</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="" style="color:#8B5CF6">str</span><span class="" style="color:#ff218c">,</span><span class=""> colors</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#404040">Vec</span><span class="" style="color:#8B5CF6">&lt;</span><span class="" style="color:#404040">Color</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#1bb3ff;font-style:italic">#[test]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">fn</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">correct_guess</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">expect_score</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;ARRAY&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;ARRAY&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">vec!</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#404040">Color</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#404040">Green</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">5</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">cargo</code> was also a welcome relief from <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2021/08/29/pockey#the-rp2040-sdk">fighting with C++ and git submodules</a>. I used <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rand</code> to pick a random target word, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">colored</code> to print colored squares to the terminal, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">serde_json</code> to read the wordlist files.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The TypeScript implementation works well, and it&#x27;s actually my preferred Wordle to use due to its simple design, infinite puzzles, and the fact that it lets me keep playing after 6 wrong guesses. In hindsight, some memoization could have improved the performance of my declarative approach, as recoloring every row on every render undoubtedly has a performance penalty. That said, it would have been a tradeoff, and I don&#x27;t think it&#x27;s necessary given the relatively small number of guesses being used.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Rust implementation is undoubtedly a bit less usable, being a CLI app, but I learned a lot about using constructs within Rust. While TypeScript had given me some intuition for how type aliases and type inference work, and C++ had given me a basic understanding of stack and heap memory, concepts such as Traits and the borrow checker were completely new to me. This wasn&#x27;t a huge project, but the variety of data structures and paradigms it involved gave me a decent birds-eye view of Rust as a language.</p></div>]]></description>
            <link>https://breq.dev/projects/wordle</link>
            <guid isPermaLink="false">/projects/wordle</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[rust]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Dynamic Music]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1920" height="1080" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fdynamic-music.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fdynamic-music.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fdynamic-music.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A screenshot of the environment.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project is a virtual environment containing several sound sources, represented as spheres. The listener, represented with a cone, can navigate around the environment to hear different combinations of the sources. Additionally, they can move the sources around within the environment.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was inspired by the way it feels to work with music in a DAW: almost like exploring some sort of space. I decided to create a virtual space which replicated that feeling, allowing anyone to play with the mixing of a song. I also made this out of a desire to work with <a href="https://threejs.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Three.js</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The app is written in vanilla JS using <a href="https://vitejs.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Vite</a> for build tooling. Each audio track was a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">wav</code> file exported from an Ableton Live set. I decided to take an object-oriented approach to the code layout, representing each sound object with an ES5 class.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Aside from a, uh, <a href="https://twitter.com/threejs/status/1484518641098014722" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">learning moment</a> causing some issues with the audio panning, most of the project was straightforward. In the end, I think I succeeded at creating the environment I set out to create, although a bit more variety in the sound sources (perhaps multiple sections of the piece which could be alternated between?) might have helped the experience not bore the listener as quickly.</p></div>]]></description>
            <link>https://breq.dev/projects/dynamic-music</link>
            <guid isPermaLink="false">/projects/dynamic-music</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[three]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[ATtiny85 Stacker Game]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/dYO6Px-RuYo/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/dYO6Px-RuYo/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A quick demonstration of the game.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a standalone game device. The game is a simple stacking game, requiring the user to press the button at the specific time to align each layer. The button is implemented by sensing skin conductivity.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to try my hand at a low-power embedded project. In the past, I had done projects which ran off of battery power, but I wanted to try to make something that could be left with the batteries in for years and still function. This required working with the sleep mode on my microcontroller of choice, which was something I hadn&#x27;t used before.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The microcontroller is an <a href="https://www.sparkfun.com/products/9378" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ATtiny85</a>, and it&#x27;s connected to an <a href="https://www.adafruit.com/product/1052" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">I2C LED matrix</a> with an HT16K33 chip.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The techniques for putting the microcontroller in sleep mode are based on this <a href="https://learn.adafruit.com/trinket-slash-gemma-space-invader-pendant/source-code" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">tutorial</a> from Adafruit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The code uses functions from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;avr/sleep.h&gt;</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;avr/power.h&gt;</code>, documented on the avr-libc site: <a href="https://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">sleep.h</a>, <a href="https://www.nongnu.org/avr-libc/user-manual/group__avr__power.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">power.h</a>. (Documentation for the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">set_sleep_mode</code> macro is notably absent, but the source is <a href="https://www.nongnu.org/avr-libc/user-manual/sleep_8h_source.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>.) The Wandering Engineer also published a more detailed <a href="https://thewanderingengineer.com/2014/08/11/pin-change-interrupts-on-attiny85/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">description</a> of the registers used (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PCMSK</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">GIMSK</code>). The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">MCUCR</code> (MCU Control Register) is documented <a href="https://web.ics.purdue.edu/~jricha14/Interrupts/MCUCR.htm" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s the meat of the implementation:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">setup</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Disable unused peripherals (Timer1 and ADC)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">power_timer1_disable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">power_adc_disable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enable interrupt on PB4 pin change</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// (Set the Pin Change Interrupt 4 bit in the Pin Change Mask register)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  PCMSK </span><span class="" style="color:#8B5CF6">|=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">_BV</span><span class="" style="color:#ff218c">(</span><span class="">PCINT4</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">awaitButton</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Send command 0x20 to the I2C display</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// (system setup / turn off system oscillator / standby mode)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  TinyWireM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">beginTransmission</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0x70</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  TinyWireM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0x20</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  TinyWireM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">endTransmission</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enable Pin Change Interrupts</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// (Set the Pin Change Interrupt Enable bit in the General Interrupt Mask register)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  GIMSK </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">_BV</span><span class="" style="color:#ff218c">(</span><span class="">PCIE</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Disable all modules</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">power_all_disable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enter power down mode</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Set the sleep mode 1 bit (SM1) in the MCU control register (MCUCR)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">set_sleep_mode</span><span class="" style="color:#ff218c">(</span><span class="">SLEEP_MODE_PWR_DOWN</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enable sleep mode</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Set the sleep enable bit (SE) in the MCU control register (MCUCR)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">sleep_enable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enable interrupts globally</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// (Set the global interrupt flag, I, in the status register, SREG)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">sei</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enter sleep mode</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">sleep_mode</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Clear the General Interrupt Mask register</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  GIMSK </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Enable Timer 0 and the USI (Serial) peripherals</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">power_timer0_enable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">power_usi_enable</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Re-initialize the display</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  TinyWireM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">begin</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c;font-style:italic">initDisplay</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve left it with the batteries in for more than a year by now, and I haven&#x27;t noticed a drop in the display brightness. I&#x27;d consider it a success, although the rationale behind some of the hardware decisions has definitely been lost to time... Why didn&#x27;t I just use an actual button? Regardless, this project certainly helped me get closer to understanding AVRs at a low level.</p></div>]]></description>
            <link>https://breq.dev/projects/tiny-stacker</link>
            <guid isPermaLink="false">/projects/tiny-stacker</guid>
            <category><![CDATA[arduino]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[power]]></category>
            <pubDate>Fri, 13 May 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Nuisance]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="902" height="854" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fnuisance.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fnuisance.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fnuisance.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The Nuisance dashboard, in light mode.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Nuisance is a dashboard I made collecting links to Northeastern University student portals and online services.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Northeastern has a few different collections of links: myNortheastern, which was disorganized and shut down, and the Student Hub, which is, frankly, filled with irrelevant information and loading spinners. I wanted an unobtrusive portal that would load quickly and link to the services I found most useful.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The page is built using React and Create-React-App, and styled with Tailwind. It doesn&#x27;t have any notable features other than a dark mode option and a setting to choose between opening links in the current tab or a new tab. Both of these settings are persisted in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">localStorage</code>. In hindsight, React was almost certainly not necessary, but I had experience with a component-based project structure and wanted to iterate quickly.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I picked Cloudflare Pages for hosting, to make sure the site loaded fast.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a lot more successful than I had thought it would be. I shared it with a few friends, and it&#x27;s accumulated a fair number of users across Northeastern. In hindsight, it isn&#x27;t that surprising to think that since I had a problem, others probably did too.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I didn&#x27;t collect a ton of user feedback: I added GitHub links for suggestions, but only a month or so after I initially shared it. This might have led to broader adoption. They didn&#x27;t take much effort to add, so in future projects, I&#x27;ll make an effort to add them initially instead of waiting for popularity to come.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also didn&#x27;t have any sort of analytics configured (since I had meant for this project to just be for myself), which made it hard to measure how much traffic the dashboard was getting. In hindsight, I guess it doesn&#x27;t really matter what the numbers say, though. This project made my day-to-day life a bit more smooth, and it helped out a few friends, too.</p></div>]]></description>
            <link>https://breq.dev/projects/nuisance</link>
            <guid isPermaLink="false">/projects/nuisance</guid>
            <category><![CDATA[react]]></category>
            <pubDate>Thu, 24 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[BotBuilder]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="864" height="573" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbotbuilder.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbotbuilder.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbotbuilder.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A couple demo commands built using Blockly.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">BotBuilder is an online tool that allows people to build custom Discord commands by dragging blocks. These commands are then added as slash commands to the user&#x27;s guild ).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After building the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/flask-discord-interactions">flask-discord-interactions</a> library, I realized how Discord&#x27;s Interactions API could enable interesting custom commands with less overhead than a traditional Gateway-based bot. Many of my friends wanted to create their own Discord bot to include custom commands, but running a Discord bot typically requires registering as a developer, finding hosting, handling tokens, installing a library, writing code, and other tasks that might prove difficult for someone inexperienced with programming. I wanted to create a service that would allow users to add custom commands to their Discord servers without any prior knowledge of bot development or code.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Users log in with their Discord account using OAuth2 to access their workspace. There, they are able to use <a href="https://developers.google.com/blockly" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Blockly</a> to create commands.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When users edit their workspace, the workspace is converted to XML and uploaded to the BotBuilder server, and if they close and reopen the workspace, their previous workspace is automatically downloaded and restored. At each upload, each command is also compiled into JavaScript, and each JavaScript function is uploaded to the BotBuilder server.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Initially, users must click the &quot;Add To Server&quot; button, which will direct them through Discord&#x27;s OAuth2 flow to authorize BotBuilder to add commands to their guild. After this, when new commands are uploaded, the BotBuilder server calls the Discord API to update the slash commands present in each of the user&#x27;s guilds, adding any new commands and removing any deleted ones. It also maintains which set of users push commands to which guilds in a Redis database.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When a command is executed in one of these Discord servers, Discord will send a POST request to BotBuilder. After verifying the cryptographic signature of this request, BotBuilder will look up which users added commands to the guild, and look through these commands for one that matches the incoming request.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After identifying the command, BotBuilder will execute the JavaScript code and return the result as a response to the POST request. To execute JavaScript, I&#x27;m using <a href="https://github.com/sqreen/PyMiniRacer/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PyMiniRacer</a>, a tool developed by <a href="https://www.sqreen.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Sqreen</a> to run and interact with Google&#x27;s <a href="https://v8.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">V8</a> JavaScript engine from Python.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I chose to use JavaScript as the compilation target for the commands for security reasons. Executing, say, Python code in a sandbox would be difficult to do securely. JavaScript, on the other hand, is designed to be run in the browser, which is an inherently sandboxed environment.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Not many of my friends ended up using this. I think the main reason was beacuse not enough features were present, and most people who wanted to make a custom command would need more functionality. Some of this was a limitation of Discord--without a bot user, the Interactions API is much more limited, and it doesn&#x27;t allow applications to look up user information, modify roles, or do other actions that most bots typically do. However, supporting additional features like custom command arguments and HTTP request blocks would have maybe turned this into a more useful service.</p></div>]]></description>
            <link>https://breq.dev/projects/botbuilder</link>
            <guid isPermaLink="false">/projects/botbuilder</guid>
            <category><![CDATA[discord]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[flask]]></category>
            <category><![CDATA[web]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Flask Discord Interactions]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1015" height="613" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot-lite.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot-lite.png&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot-lite.png&amp;w=2048&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Some commands for Breqbot Lite, a bot I made with this library.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Recently, Discord introduced a new Slash Commands feature that allows bots to integrate using webhooks. This is a library that handles registering the commands, receiving interactions, sending responses, sending followup messages, and including message components like clickable buttons in your message. It&#x27;s written as a Flask extension, so you can add other pages to the app and handle scaling/serving like any other Flask app.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most Discord bots and libraries use a Bot user to connect to the Discord API. Bot users interact with Discord in a similar way to actual Discord users: they connect over a WebSocket and then send and receive events such as messages. This approach works well for basic bots, but it makes it difficult to scale. Alternatively, webhook-based bots can be deployed behind a load balancer and scaled up or down as needed without worrying about overloading the websocket or allocating different servers to different processes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, the webhook approach is significantly more limited. Webhook bots can&#x27;t manage channels, reactions, direct messages, roles, or most of the other features in Discord. However, for basic bots that don&#x27;t need these features, webhook bots can be easier to develop and deploy.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The library is designed to be similar to the popular <a href="https://github.com/Rapptz/discord.py" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Discord.py</a> library. It&#x27;s probably better to show than to tell:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> os</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> flask </span><span class="" style="color:#8B5CF6">import</span><span class=""> Flask</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> flask_discord_interactions </span><span class="" style="color:#8B5CF6">import</span><span class=""> DiscordInteractions</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">app </span><span class="" style="color:#8B5CF6">=</span><span class=""> Flask</span><span class="" style="color:#ff218c">(</span><span class="">__name__</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">discord </span><span class="" style="color:#8B5CF6">=</span><span class=""> DiscordInteractions</span><span class="" style="color:#ff218c">(</span><span class="">app</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">app</span><span class="" style="color:#ff218c">.</span><span class="">config</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_CLIENT_ID&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> os</span><span class="" style="color:#ff218c">.</span><span class="">environ</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_CLIENT_ID&quot;</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">app</span><span class="" style="color:#ff218c">.</span><span class="">config</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_PUBLIC_KEY&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> os</span><span class="" style="color:#ff218c">.</span><span class="">environ</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_PUBLIC_KEY&quot;</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">app</span><span class="" style="color:#ff218c">.</span><span class="">config</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_CLIENT_SECRET&quot;</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> os</span><span class="" style="color:#ff218c">.</span><span class="">environ</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;DISCORD_CLIENT_SECRET&quot;</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">@discord</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">command</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">ping</span><span class="" style="color:#ff218c">(</span><span class="">ctx</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;Respond with a friendly &#x27;pong&#x27;!&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Pong!&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">discord</span><span class="" style="color:#ff218c">.</span><span class="">set_route</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;/interactions&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">discord</span><span class="" style="color:#ff218c">.</span><span class="">update_slash_commands</span><span class="" style="color:#ff218c">(</span><span class="">guild_id</span><span class="" style="color:#8B5CF6">=</span><span class="">os</span><span class="" style="color:#ff218c">.</span><span class="">environ</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;TESTING_GUILD&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">if</span><span class=""> __name__ </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#1bb3ff">&#x27;__main__&#x27;</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    app</span><span class="" style="color:#ff218c">.</span><span class="">run</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">discord.command()</code> decorator creates a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SlashCommand</code> and adds it to the application, and the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">discord.set_route()</code> function adds an HTTP route that handles interaction events. The library will automatically register the commands and remove old ones on launch. When it receives an interaction from Discord, it will verify the signature, parse the command options, run the command, and return the result.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was one of the first OAuth2 projects I made, which was cool. It works well enough for my basic testing bot. Overall, I&#x27;m pretty proud of this one: I saw a gap where a library didn&#x27;t exist, and I developed something to fill it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m also really glad to see that a small community has sprung up around this library. I listed it in the official Discord Developer Documentation, among the numerous other Slash Command libraries. So far, four other people have contributed code to the project through pull requests, and 22 issues have been filed in the issue tracker. It&#x27;s been an interesting experience to receive a bug report or feature request from a community member and then figure out how to prioritize it and how best to patch it.</p></div>]]></description>
            <link>https://breq.dev/projects/flask-discord-interactions</link>
            <guid isPermaLink="false">/projects/flask-discord-interactions</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[flask]]></category>
            <category><![CDATA[discord]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[3D Printer Light Tower]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1720" height="1290" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flight-tower.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Flight-tower.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flight-tower.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">NeoPixels never look good on camera.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a small indicator light strip that I mounted next to my 3D printer. It indicates if the printer is ready, printing, or in an error state.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In June 2021, I started a summer job in the Texas Instruments semiconductor fab in South Portland. There were a lot of different machines in the fab, but they all had one standardized light tower design. This helped me learn how to operate them more quickly, transfer my knowledge between different areas, and understand the status of a machine at a glance.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I noticed that the indicators shown for a fab machine (load, unload, error) were similar to those I might want to show for a 3D printer. So, I set about building my own &quot;light tower&quot; to attach to my 3D printer.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I used Adafruit&#x27;s NeoPixel library to write a Python script that changes the status displayed on the light tower. However, this script needs to run as root, since Adafruit&#x27;s library uses DMA (direct memory access) to control the Pi&#x27;s PWM module.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I then wrote an Octoprint plugin that handles printer events. I used the <a href="https://docs.octoprint.org/en/master/plugins/mixins.html#eventhandlerplugin" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">EventHandlerPlugin mixin</a> to write a basic Python script that called the NeoPixel script when necessary.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here is when an issue arose: Octoprint runs with a user account, but the NeoPixel script needs to run as root. I needed some way to allow the Octoprint user account to execute a program as the root user.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to use the Unix &quot;setuid&quot; system to allow the Octoprint user to invoke the NeoPixel script with the permissions of the root user. Since setuid can&#x27;t be used for scripts, I wrote a wrapper function to pass along the arguments and run the NeoPixel script as root.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">#</span><span class="" style="color:#8B5CF6">include</span><span class="" style="color:#8B5CF6"> </span><span class="" style="color:#1bb3ff">&lt;string.h&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">#</span><span class="" style="color:#8B5CF6">include</span><span class="" style="color:#8B5CF6"> </span><span class="" style="color:#1bb3ff">&lt;stdio.h&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">#</span><span class="" style="color:#8B5CF6">include</span><span class="" style="color:#8B5CF6"> </span><span class="" style="color:#1bb3ff">&lt;stdlib.h&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">#</span><span class="" style="color:#8B5CF6">include</span><span class="" style="color:#8B5CF6"> </span><span class="" style="color:#1bb3ff">&lt;sys/types.h&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">#</span><span class="" style="color:#8B5CF6">include</span><span class="" style="color:#8B5CF6"> </span><span class="" style="color:#1bb3ff">&lt;unistd.h&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">int</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">main</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">int</span><span class=""> argc</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">char</span><span class="" style="color:#8B5CF6">*</span><span class=""> argv</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">setuid</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    argv</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./tower.py&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">execv</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;./tower.py&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> argv</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With this in place, the chain of Octoprint -&gt; Plugin -&gt; Setuid Wrapper -&gt; NeoPixel Script -&gt; NeoPixels worked perfectly.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Generally, none of my projects have required an understanding of Linux permissions and libc functions like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">setuid</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">exec</code>. A lot of my knowledge in this area was built up by watching <a href="https://www.youtube.com/channel/UClcE-kVhqyiHCcjYwcpfj9w" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">LiveOverflow</a>. It was really fun to finally actually put some of this knowledge to use, and writing a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">setuid</code> program myself helped me better understand some of the security issues involved and the other specifics of this system.</p></div>]]></description>
            <link>https://breq.dev/projects/light-tower</link>
            <guid isPermaLink="false">/projects/light-tower</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[python]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Links]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="558" height="461" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flinks%2Fsignin.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flinks%2Fsignin.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flinks%2Fsignin.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The sign-in screen.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="797" height="563" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flinks%2Fdashboard.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Flinks%2Fdashboard.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flinks%2Fdashboard.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The dashboard for the URL shortener.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Exactly what it says on the tin: a basic URL shortening service that allows changing the destination of the URL after creating it.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had done some server-side-rendering work with Flask before, but I hadn&#x27;t ever approached it from the JavaScript side of things. I wanted to understand some of the frameworks that are used to perform SSR using Node.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At the time I made this, the college application season wasn&#x27;t that far behind me, and I remember seeing mail from some colleges using a plus sign from their domain as a URL shortener, such as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">https://wpi.edu/+FJI3DE</code>. This seemed like a cool idea, since it wouldn&#x27;t interfere with existing routes but would provide short URLs on a recognizable and trusted domain.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">URLs are stored in <a href="https://redis.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Redis</a>, since a key-value store seemed like a great fit for this scenario. Routing is handled using <a href="https://koajs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">koa</a>. For requests to the redirect URLs, this is all that happens.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the login and dashboard pages, I used templates written in <a href="https://mozilla.github.io/nunjucks/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nunjucks</a>, a JavaScript templating language similar to Python&#x27;s <a href="https://www.palletsprojects.com/p/jinja/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">jinja2</a> (used with <a href="https://flask.palletsprojects.com/en/2.0.x/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Flask</a>).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Authentication was handled with JWTs as cookies, and I used the GitHub OAuth API instead of storing usernames and passwords. I didn&#x27;t use a client-side JavaScript framework, just plain old HTML forms and a bit of vanilla JS to handle some show/hide buttons.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project made me shift my thinking a lot. In many regards, I&#x27;d been spoiled by front-end frameworks. Working without one, I had to think about how I could use the platform to accomplish my goals. This in turn gave me a better understanding of concepts like JWTs, cookies, and HTML forms.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It was cool to use platform features like forms and cookies to handle data submission and authentication without client side JS. Contrast this with <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/flowspace">flowspace</a>, in which I just passed the token as an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Authorization</code> header for every <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fetch</code> POST request I made. This was a pretty simple project, but I&#x27;m glad I took it on.</p></div>]]></description>
            <link>https://breq.dev/projects/links</link>
            <guid isPermaLink="false">/projects/links</guid>
            <category><![CDATA[node]]></category>
            <category><![CDATA[koa]]></category>
            <category><![CDATA[redis]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Picto]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="643" height="943" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpicto.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpicto.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpicto.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a clone of Pictochat built on top of Web technologies.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<div class="ml-12"><blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wish we never met<br/> <strong>We broke up on PictoChat, crying on my DS</strong><br/> I went to a birthday party for one of her friends<br/> And now that this is over I can hate them, I don&#x27;t have to pretend</p>
</blockquote><div class="ml-12"><p class="mx-auto my-4 max-w-prose font-body text-lg ">- Glitch Gum, &quot;NEVER MET!&quot;</p></div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I grew up with a Nintendo DS, so it&#x27;s no surprise I have a ton of nostalgia for Pictochat. And when a certain hyperpop song rekindled that nostalgia, I wanted to find a way to experience Pictochat again.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The project is mostly just a React single-page-application (although there&#x27;s a small WebSocket server component <a href="https://github.com/breqdev/pictoserver/blob/main/index.js" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a> to rebroadcast messages).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I didn&#x27;t strip any assets from Pictochat itself, and I&#x27;m not much of a sound or icon designer, so I made do with what I could find. I picked similar sounds from <a href="https://github.com/breqdev/pictoserver/blob/main/index.js" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">material.io</a> and icons from <a href="https://fontawesome.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Font Awesome</a>. Some of them are a better match than others, but overall, they match up pretty well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I made an effort to have usable keyboard navigation. The original DS allowed using either the stylus or control pad for navigating the interface, so I wanted this project to have a similar experience.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also tweaked the onboarding flow a bit. In the original Pictochat, the name and theme color of the user was read from the DS system settings. Since I&#x27;m only cloning Pictochat itself, I instead prompted for these during onboarding. I also tweaked the chatroom mechanics somewhat. The original Pictochat used the Nintendo Low Latency Protocol to create chatrooms with nearby DS handhelds within range over 2.4GHz. While a similar system would be fun to implement on dedicated Linux boxes (<a href="https://wiki.archlinux.org/title/ad-hoc_networking" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">IBSS</a> + <a href="https://www.open-mesh.org/projects/batman-adv/wiki" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">batman-adv</a>, anyone?), I certanly couldn&#x27;t implement it in a Web browser, so chatrooms are global.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One of my favorite aspects of Pictochat was the closed-ness of the rooms: even though you might be in a room with unfamiliar people (e.g. at an event), you could be certain it was a relatively small group that you could get to know. Having four global chatrooms seemed counter to this, so I insted opted to let chatroom names be any arbitrary string. My hope was that people would choose mostly-unique names, keeping the number of users per chatroom relatively low.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Building this as a React app and styling it with Tailwind was mostly straightforward. It was a bit difficult to get the viewport string correct such that the window wouldn&#x27;t become narrower on mobile -- for once, we don&#x27;t actually want the site to be responsive, since it has to exactly match the layout of the original Pictochat! Also, keeping the state of the canvas in the message compose box was tough, since the state of the image couldn&#x27;t be extracted from the canvas element itself. As a workaround, I passed a ref object <em>down</em> to the canvas, which assigned it to a dispatch function. Then, the parent component could dispatch commands down into the child and request state to flow up from it. It&#x27;s ugly, but I can&#x27;t think of a better way of doing things: I can&#x27;t exactly change how the platform works, and mixing multiple data models never works well.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It isn&#x27;t perfect, but it&#x27;s accurate enough that I was able to relive some of my childhood: typing a bunch of text and scribbling furiously with the pen tool to make a message completely black, copying and editing a message to write all over it, and dragging letters all over the page as decoration.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The WebSocket connection had some reliability issues, and I think my reconnection logic might have been broken somehow. Other than that, I was pretty happy with how everything turned out. It didn&#x27;t blow up, but it helped me and my friends scratch that nostalgic itch, which was nice.</p></div>]]></description>
            <link>https://breq.dev/projects/picto</link>
            <guid isPermaLink="false">/projects/picto</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[node]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Cards]]></title>
            <description><![CDATA[<div class="e-content font-body"><iframe class="block mx-auto my-8 rounded-2xl mt-4" height="300" width="500" src="https://cards.api.breq.dev/card/219156620534663947.html"></iframe>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a service to generate custom &quot;cards&quot; based on a defined template and user-supplied fields.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While working on <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/breqbot">Breqbot</a>, I wanted to replicate the &quot;rank card&quot; idea provided by bots like <a href="https://mee6.xyz/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MEE6</a>, but with user-supplied information and images instead.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by writing a program using PIL that would take in a user&#x27;s name, bio, and profile image, and generate a basic PNG. I was frustrated by the process and the end result. I had to manually implement things I had taken for granted in the world of web-dev, such as text wrapping and emoji support. The process of implementing and modifying the card templates was time-consuming and tedious. Additionally, when I tried to include these rudimentary images on Breqbot&#x27;s website, I needed to redo the entire layout in HTML and CSS.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had the idea of creating a standalone service to generate these cards based on a predefined template and output them to either an IFrame or an image file. The resulting output could be used anywhere: sent as a Discord message, included in a GitHub README, or embedded in a website.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The service will render an HTML template with the user-provided parameters. Then, if an image file is requested, it will use <a href="https://github.com/pyppeteer/pyppeteer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">pyppeteer</a> to take a screenshot of the HTML template using Chrome.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s also possible to &quot;freeze&quot; a card, preserving its screenshot on the server and returning a permanent link/URL to the card. This avoids having to use pyppeteer for every request for the card. To generate the card IDs, I&#x27;m using another service I made, <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/snowflake">Snowflake</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wasn&#x27;t originally a huge fan of using a headless browser in the server-side, as it seemed like it would be a waste of resources and using the wrong tool for the job, but the service ended up working pretty well, although the time-to-first-byte is, predictably, pretty poor compared to the other projects I&#x27;ve made.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Performance aside, the process of developing new cards is much easier now. At the time of writing, I&#x27;ve pretty much only dipped my toes into web development, but I was able to make a few templates pretty quickly that looked much better than the old PIL tool ones.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While integrating this service with Breqbot and analyzing how it could be used, I noticed that most use cases will generate a card once and then embed it repeatedly. For instance, users will request each others&#x27; cards on Breqbot more often than they will update their own, and cards put in e.g. GitHub profiles are typically created once and left as-is for a while. As such, it&#x27;s kind of wasteful to regenerate the card for every request, so I implemented the &quot;Freezing&quot; functionality. This was a cool experience: deploying a project, seeing how it was used, and then adding functionality where it was lacking.</p></div>]]></description>
            <link>https://breq.dev/projects/cards</link>
            <guid isPermaLink="false">/projects/cards</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[flask]]></category>
            <category><![CDATA[puppeteer]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Mini-ITX Computer Case]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3024" height="2456" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpc-case%2Fphoto.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpc-case%2Fphoto.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The finished product had about the same footprint as my laptop.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In late 2018/early 2019, I decided to build a desktop computer for use doing 3D rendering and experimenting with machine learning.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, I didn&#x27;t document my work nearly as well as I should have, so this writeup is going to be a bit heavy on the renders and light on the photos.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a custom-built PC case that houses a mini-ITX motherboard, SFX power supply, and small-form-factor GPU. It was built out of a mix of wooden parts and 3D-printed parts that I designed.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Around this time, I had started taking 3D rendering at MSSM, and I was disappointed with the poor rendering performance of my laptop. I decided that I wanted to build a more powerful computer that would better handle tasks like 3D modeling, rendering, and deep learning. In order to get the best sustained performance at my price point, I decided to go with a desktop instead of a higher-end laptop or a NUC.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, I wasn&#x27;t a big fan of how large most desktop computers were. Most PC cases seemed extremely large, which wasn&#x27;t convenient for me at all, since I had to go back and forth to MSSM every month at the time. While form factors like ITX (motherboards) and SFX (power supplies) had started to take off, even cases built for them seemed excessively large.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to build my own case, with a goal of making something no larger than a few vertically-stacked binders. I wanted it to be able to fit in my backpack or suitcase to make transportation easy.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The case consists of three wooden panels (the top, bottom, and front) that all screw into the 3D-printed interior structure. Because my 3D printer has only 120mm of build plate space, the interior is split into 6 separate sections which are held together by their mutual attachment to the wooden panels.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1436" height="1144" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpc-case%2Fexploded.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpc-case%2Fexploded.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpc-case%2Fexploded.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The case contains two distinct airflow zones--one for the GPU and one for the CPU. The GPU slots into the left side of the case, connecting to the motherboard using a riser. The PSU sits directly underneath it. Additionyally, there is space for an SSD alongside the fan.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1570" height="734" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpc-case%2Frightside.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpc-case%2Frightside.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpc-case%2Frightside.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the left side, a hard drive sits underneath the motherboard, and there is space for an additional drawer for storing USB cables or other small parts. Above these, the motherboard sits on standoffs, and an SSD can attach to the front. This area has its own fan which blows air mostly above the motherboard, but some goes towards the hard drive as well.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1552" height="729" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpc-case%2Fleftside.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpc-case%2Fleftside.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpc-case%2Fleftside.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the far corner of the render, you can see a small keyhole shape. This is a place where power button, status LED, or other types of modules can be inserted. I only ever made one such module, a simple on/off switch.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I used this case for over a year, and it held up pretty well. By the end of its life, the power button had broken, and the screws were starting to strip inside the 3D-printed plastic. However, structurally, it held up well, even after getting roughly tossed into a suitcase many times.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After I came back home from MSSM, I transferred my rig back over to an off-the-shelf case, since I had plenty of space for it. That said, I certainly wouldn&#x27;t call the project a failure--it was a great case for the 2 years or so that I used it, and I learned a lot about 3D printing in the process. Specifically, this project made me really consider the structural qualities of a 3D print, and I had to choose the orientation of each piece carefully in order to ensure each one was strong. While I used up plenty of filament in the process, by the end, this was something that I&#x27;m really proud of!</p></div>]]></description>
            <link>https://breq.dev/projects/itx-case</link>
            <guid isPermaLink="false">/projects/itx-case</guid>
            <category><![CDATA[hardware]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[MakerGamer]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="530" height="942" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Farchives%2Fmakergamer.jpg&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">MakerGamer running on a PocketCHIP</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">MakerGamer was a virtual video game console designed to make game development as accessible as possible and to encourage the sharing of games. It could play games written in Scratch, Python, and JavaScript, and it provided basic tools to modify the games people downloaded. I wrote it with the intent of running it on Next Thing Co.&#x27;s <a href="https://www.theverge.com/circuitbreaker/2016/7/19/12227806/pocketchip-review-portable-linux-computer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PocketCHIP</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project was inspired by Pico-8, a “fantasy console” that allowed people to write retro-feeling games for modern hardware. However, the learning curve for using Pico-8 was high, and I thought it would be cool to create something in the style of Pico-8 but that would be suitable for people learning to code for the first time. I set out to create a similar “fantasy console” that could play games written in Scratch but felt like a traditional video game console.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I designed and programmed an interface for downloading Scratch projects by the project ID, adding them to a virtual library, and playing them on demand, using <a href="https://phosphorus.github.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Phosphorous</a> to run then without Adobe Flash. Python games written using PyGame were executed with parameters set to make PyGame full screen. Web-based games were downloaded and opened in a web browser. The user interface (main menu, project editors, etc) and the code editor were written in Python using PyGame.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It worked well enough for me to demo it to people at the STEM fair. But it didn&#x27;t really have that much actual use, as all the games could be played in a web browser anyway. I eventually scrapped plans to finish the project-editing functionality, as it wasn&#x27;t practical to write a code editor, image editor, and etc. that only ran on a tiny screen anyway.</p></div>]]></description>
            <link>https://breq.dev/projects/makergamer</link>
            <guid isPermaLink="false">/projects/makergamer</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[scratch]]></category>
            <category><![CDATA[javascript]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[R2D2 Clone]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="530" height="942" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Farchives%2Fr2d2.jpg&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Farchives%2Fr2d2.jpg&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Farchives%2Fr2d2.jpg&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">If you&#x27;re wondering, the chassis is an old Play-Doh bucket.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Exactly what it says on the tin: a remote-controlled robot built to look like R2D2. As time went on, I did add some more advanced features, such as text-to-speech and video streaming.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was made for several occasions: the STEM fair, and Halloween later that year when I went trick-or-treating as Luke Skywalker.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started with the guts of an earlier robot I had built: two Lego NXT motors, connected to a Raspberry Pi through an H-Bridge motor driver. I spent a lot of time adding features to this robot: it could be controlled using a Wii remote, a smartphone app, or an interactive website. It had speech synthesis software and an internal speaker, a camera mounted to allow video streaming, and a few LEDs for blinkenlights.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By the end of the project, I had switched to using an NXT brain to control the motors, connected over USB to the Raspberry Pi.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It did its job and did it well. This project was one of the first significant software projects I worked on, and it taught me how to design and organize a program longer than just a few lines.</p></div>]]></description>
            <link>https://breq.dev/projects/r2d2</link>
            <guid isPermaLink="false">/projects/r2d2</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Rave Choker / Outshine]]></title>
            <description><![CDATA[<div class="e-content font-body"><iframe title="gif from giphy" src="https://giphy.com/embed/Ztwx0SfEd2tgDE7J2w" width="270" height="480" frameBorder="0" allowfullscreen="" class="mx-auto my-4"></iframe>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The latest revision of the choker, displaying a multi-color fade/wipe animation.</p>
<iframe title="gif from giphy" src="https://giphy.com/embed/WHmc9HI2lXWgnEgY7Y" width="270" height="480" frameBorder="0" allowfullscreen="" class="mx-auto my-4"></iframe>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The first revision of the choker, displaying a simple back-and-forth animation.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project consists of three parts:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://github.com/breqdev/outshine" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">outshine</a>, an Arduino program that displays animations on a NeoPixel strand</li>
<li class="my-2 pl-2"><a href="https://github.com/breqdev/OutshineApp" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">OutshineApp</a>, an Android app to communicate with an Arduino running Outshine via USB serial</li>
<li class="my-2 pl-2">the rave choker, the physical device that I built which runs Outshine firmware</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="outshine">Outshine</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Outshine itself was based on some work I did for NU Rover, Northeastern University&#x27;s team which competes in the Mars Society&#x27;s University Rover Challenge. On the rover, LEDs are controlled by an ATmega328PB separate from the primary STM32 microcontroller. As such, to implement the NeoPixel handling, I essentially had free reign over this chip.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Functionally, the rover is required to display different colors to indicate its control mode (teleoperated or autonomous) and status about its autonomous navigation. It does not need to show animations, but I included them anyway because I figured they could be useful for displaying more detailed status information.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rave-choker">Rave Choker</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I got a ticket to a <a href="https://twitter.com/officialrezz" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Rezz</a> show, and I wanted to incorporate some flashy LEDs in my outfit somehow. Rezz is known for her LED glasses which display a spiral pattern. I wanted to create something similarly colorful and animated, but not a direct reimplementation of the glasses.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The process started with me standing in front of a mirror and holding a lit LED strip up to different parts of my body. Eventually, I settled on a choker, because I liked the look of it the best.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Originally, I had intended for the choker to be controlled via Wi-Fi by broadcasting its own network and exposing a server with a web UI. I had some familiarity with this architecture, as it&#x27;s similar to how I built the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">wall matrix</a> project.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At the time, I had recently bought a limited-edition <a href="https://www.adafruit.com/product/5299" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">pink RP2040 Feather</a> board from Adafruit, and I really wanted to use it for a project. This board doesn&#x27;t have Wi-Fi or Bluetooth, so I figured I would add an additional Pi Zero to handle the networking.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With the added bulk of another board in mind, I decided to explore putting the boards and battery off of my neck and into a hat.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1148" height="1148" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fhat.jpg&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Foutshine%2Fhat.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Foutshine%2Fhat.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This worked to some extent, but the stretchiness of the hat didn&#x27;t pair well with the fragile ribbon of the LED strip. In the end, I ditched the idea as I thought it seemed too fragile for extended use.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="outshineapp">OutshineApp</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Going back to the drawing board, I decided to try leaving the Feather board on my neck and directly attaching it to my phone over USB. The phone would interface with the board over serial and provide power to it (removing the need for a dedicated battery).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tried to use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">WebSerial API</a> for this, but was hit with a really stupid issue involving Chrome on Android:</p>
<div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As such, I switched to a native app. I built it in React Native, because I already had some familiarity with React.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="redesign">Redesign</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Tragically, this first revision of the choker was left slightly trampled after I went a little too hard in a <a href="https://twitter.com/fo0dhouse" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">food house</a> moshpit. I was able to quickly repair the broken solder joints, and the LED strip was barely damaged (somehow only the red channel of the last pixel was broken), but I still realized that I needed to rethink the design.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had the following goals in mind for the redesign:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Remove the need for a tether between the phone and the choker (as this could get dangerous in a crowd, or yanking it could cause the choker to come off of me)</li>
<li class="my-2 pl-2">Make the clasp at the back of the choker more sturdy</li>
<li class="my-2 pl-2">Remove the silicone outer layer from the LEDs</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="outshine-1">Outshine</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Outshine firmware takes in commands over I2C, UART, or Bluetooth. Initially, commands were four bytes long:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">[1 byte] Red channel</span></div><div class="" style="color:#404040"><span class="">[1 byte] Green channel</span></div><div class="" style="color:#404040"><span class="">[1 byte] Blue channel</span></div><div class="" style="color:#404040"><span class="">[1 byte] Animation command</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Later, I reworked the protocol to use a two-byte indicator before each byte, to prevent the ends of the protocol from getting out of sync:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">01</code>: Start Transaction, remaining 6 bits are the animation ID</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">10</code>: Color data, next 6 bits are a portion of an RGB color
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Color data is shifted in by this command, so any existing color data will be shifted left by 6 bits</li>
</ul></div>
</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">11</code>: Final color data, next 6 bits are color data and this is the end of an RGB color
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">This can be followed by either additional colors (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">10</code>) or the end of the transaction (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">00</code>)</li>
</ul></div>
</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">00</code>: End Transaction</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I2C support was added for Rover, UART support was added for debugging the Rover but was later used for the rave choker, and Bluetooth support was added for the V2 choker.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The driver relies on one of two libraries, depending on the architecture:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><a href="https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library-installation" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit_NeoPixel</a> for ATmega Arduino boards</li>
<li class="my-2 pl-2"><a href="https://learn.adafruit.com/adafruit-neopxl8-featherwing-and-library/neopxl8-arduino-library" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit_NeoPXL8</a> for the RP2040, to take advantage of the chip&#x27;s <a href="https://hackspace.raspberrypi.com/articles/what-is-programmable-i-o-on-raspberry-pi-pico" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PIO</a> capabilities</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The code makes use of a table of function pointers to control the active animation. The animation state (e.g. current active pixel in a wipe animation, current frame in a fade animation) is persisted across a shared struct to make transitions between animations more smooth.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="outshineapp-1">OutshineApp</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="792" height="1760" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fapp.jpg&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Foutshine%2Fapp.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Foutshine%2Fapp.jpg&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The app is built in React Native, using <a href="https://github.com/melihyarikkaya/react-native-serialport" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">react-native-serialport</a> to handle the USB serial communication and <a href="https://github.com/dotintent/react-native-ble-plx/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">react-native-ble-plx</a> to handle Bluetooth. I&#x27;m just using off-the-shelf components for the color wheel, buttons, and brightness slider. I didn&#x27;t even bother to build a production version; I just keep the development <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.apk</code> on my phone.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1080" height="2400" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fapp_v2.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Foutshine%2Fapp_v2.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Foutshine%2Fapp_v2.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The V2 version of the app switched to simple color swatches and added support for multi-color configurations (complete with some pride-themed presets). This was much easier for me to manipulate quickly.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rave-choker-1">Rave Choker</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2347" height="1760" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fchoker.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Foutshine%2Fchoker.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The V1 rave choker itself consisted of a strand of NeoPixels connected to an Adafruit <a href="https://www.adafruit.com/product/5299" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Pink RP2040 Feather</a>. I used a <a href="https://en.wikipedia.org/wiki/Banana_connector" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">&quot;banana connector&quot;</a> as a clasp. It was wired to ground on both sides, so it wasn&#x27;t electrically functional.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2347" height="1760" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fboard.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Foutshine%2Fboard.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The body of the choker was housed in a 3D-printed case, made from two parts that screw together using threaded inserts. The case has an open top to show off the pink RP2040, because I thought it looked cool.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3000" height="4000" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fold_choker_trans.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Foutshine%2Fold_choker_trans.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The V2 rave choker also uses a 3D-printed frame, but it completely encloses the board. This frame also includes a battery, allowing for operation without a tether. I continued to use a Feather, but I opted for the <a href="https://www.adafruit.com/product/4062" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nRF52840</a> variant for Bluetooth support.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3000" height="4000" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fnew_choker.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Foutshine%2Fnew_choker.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I used fake leather material and metal snaps to provide the structure of the choker, and I attached the NeoPixels to the material using E6000 adhesive, loosely following <a href="https://learn.adafruit.com/neopixel-led-harness-bra" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this guide for a harness bra</a> from Adafruit. I again blacked out the copper contacts on the front of the LED strip, but this time, I used black nail polish instead of eyeliner. The result vaguely resembles a studded look, and the choker doesn&#x27;t look out of place even with the lights turned off.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="3456" height="4608" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Foutshine%2Fnew_choker_off.jpg&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Foutshine%2Fnew_choker_off.jpg&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It worked well at the Rezz show! In fact, a cute transfem noticed it, and we ended up talking and dancing together for most of the night :)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had initially been worried about the battery life being an issue, but as long as the LEDs aren&#x27;t solidly on full-white, it doesn&#x27;t seem to be a huge problem.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I was also a bit worried about the &quot;crowd-safety&quot; of it--what if someone yanks on the cord?--but it came unplugged easily without yanking on my neck.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also wore the choker to <a href="https://www.gaybashdboston.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GAY BASH&#x27;D</a>, a combination DJ set and drag show. I made a few revisions to the setup prior to this event, adding a brightness slider to OutshineApp and the 3D-printed case to the choker itself.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After getting the Bluetooth version up and running, I also wore the choker to Carpenter Brut and SVDDEN DEATH shows, both of which had much more intense moshing than shows I had been to in the past. That said, the choker held up well! It seems to have some level of beer-spill-resistance, which is essential.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Overall, I think I really achieved the goal I started with. The choker is something flashy, stylish, and uniquely &quot;me,&quot; and it stands out in the perfect way among a crowd at an EDM show. Also, the Outshine project will undoubtedly prove a useful starting point for future experimentation with NeoPixels.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Starting off with a tethered solution wasn&#x27;t ideal (for instance, it made going through security a bit of a hassle). Even though the tether wasn&#x27;t too much of a hassle at more chill shows, it still was much more convenient to have a Bluetooth-based design.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s also a bit cumbersome to change the animation, requiring me to unlock my phone and open the app. I only really wanted to change it every few songs, but it was still frustrating to have to stop raving to fiddle with a smartphone app. Physical controls would help remedy this, at the cost of more complexity. At the end of the day, I think the tradeoffs I made paid off.</p></div>]]></description>
            <link>https://breq.dev/projects/outshine</link>
            <guid isPermaLink="false">/projects/outshine</guid>
            <category><![CDATA[arduino]]></category>
            <category><![CDATA[c++]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[react]]></category>
            <pubDate>Sun, 13 Feb 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[WorkerSocket]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> i workersocket</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a library I made to run a WebSocket inside of a Web Worker in the browser. It exports a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WorkerSocket</code> object which behaves as closely as possible to a browser WebSocket.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wrote this while implementing my <a href="https://github.com/breqdev/roslib/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">fork of roslib</a>, a library for communicating with a <a href="https://www.ros.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ros</code></a> server through the <a href="http://wiki.ros.org/rosbridge_suite" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rosbridge</code></a> protocol. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">roslib</code> is typically used to build a web-based monitoring UI for some type of robot.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The original <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">roslib</code> hijacked a browserify library called <a href="https://github.com/browserify/webworkify" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">webworkify</code></a> to run a WebSocket through a Web Worker, but <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">webworkify</code> doesn&#x27;t bundle with Vite or Webpack 5.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The reason <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">roslib</code> defaults to putting a WebSocket inside of a Web Worker is to make sure that data is pulled away from the server as quickly as possible, even if it ends up building up in the web worker. Due to an oversight in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rosbridge</code> server, if a client can&#x27;t pull information quickly enough, then the server will use excessive resources caching data. This <a href="https://github.com/RobotWebTools/roslibjs/pull/317" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">pull request</a> for the original <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">roslib</code> describes the issue in more detail. Generally, the software on the robot is more important than the monitoring UI, so offloading the queueing to a background thread in the UI client makes sense.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The library consists of two parts: the web worker itself, and the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WorkerSocket</code> implementation.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WorkerSocket</code> class mimics the API of a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WebSocket</code>, but forwards messages to the web worker. The web worker then handles the actual socket. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WorkerSocket</code> class maintains an array of listeners for each event and calls each one as necessary. It also generates a unique ID for each <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">WorkerSocket</code> class instantiated, which is then sent along with all messages to the web worker. The worker maintains a mapping from IDs to socket instances.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The web worker is constructed using an object URL. This technique allows the worker to be bundled with any bundler without any special handling. It is somewhat unintuitive, though, using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Function.toString()</code> to turn the worker implementation into a string, then placing it into an <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">immediately-invoked function expression</a> using string manipulation. This is then turned into an object URL from which the worker is loaded.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> workerURL </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">URL</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">createObjectURL</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Blob</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;(&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> workerImpl</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">toString</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;)();&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;text/javascript&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> worker </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Worker</span><span class="" style="color:#ff218c">(</span><span class="">workerURL</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Testing is performed using Chai in the browser (and using Puppeteer to run headlessly).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It seems to work well. That said, there are so many edge cases with the behavior of WebSockets in the browser that I&#x27;m a bit hesitant to use it. I&#x27;ve written plenty of tests to cover common use cases, and I don&#x27;t know what I would add, but I also don&#x27;t feel like the test suite is entirely complete.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve published the result to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm</code> though, in case others want to make use of this.</p></div>]]></description>
            <link>https://breq.dev/projects/workersocket</link>
            <guid isPermaLink="false">/projects/workersocket</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[networking]]></category>
            <pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Universal Hooks]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;ve studied discrete math, you might be familiar with the concept of a &quot;universal gate.&quot; You might have also heard that the NAND gate and NOR gate are each universal gates, since you can create any other gate by composing many NANDs or NORs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">React Hooks compose just like logic gates. If you use multiple hooks, they shouldn&#x27;t interfere with each other, just as multiple logic gates in a circuit can operate independently. Any collection of Hooks can be abstracted away into a new custom hook, just as a collection of logic gates within a circuit can be abstracted into a single unit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While we&#x27;re not going to find a truly universal hook, we will show how a few basic hooks can be used to implement most of the others. We&#x27;re also not going to aim for complete API compatibility -- each of our hooks will do its best to cover a few common use cases.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This post is intended to be interesting reading for those with some experience with React, and to demonstrate some of the principles of hook composition. Thus, some familiarity with React is assumed.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="our-building-blocks">Our building blocks</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <a href="https://reactjs.org/docs/hooks-reference.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">React docs</a> list three &quot;Basic Hooks.&quot; Let&#x27;s step through and analyze what each of these do.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usestate"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This hook will keep track of some value between renders. It also provides a function to set the value. Calling this function will trigger a re-render with the updated value.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="useeffect"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useEffect</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This hook will run a function after the first render, before unmounting the component, and whenever one of the provided values changes.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usecontext"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useContext</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This hook uses the Context API to deeply pass values down the component tree. Since none of the other built-in hooks use the Context API, we won&#x27;t need to use this.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="implementations">Implementations</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve taken the liberty to rearrange this list a bit from the docs, since we&#x27;ll use some of the earlier hooks to build some of the later ones.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="useref"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A Ref can be thought of as a &quot;box&quot; that holds a value. Refs are simply objects of the form:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">current</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">...</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This allows you to update the value of the ref, and any code with a reference to it will see the updated value. For instance, consider this code:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">let</span><span class=""> count </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">makePrinter</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> printCount </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">makePrinter</span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">printCount</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// outputs 0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">count </span><span class="" style="color:#8B5CF6">+=</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">printCount</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// outputs 0</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is admittedly quite contrived. However, in React, this comes up much more often. If you aren&#x27;t careful, your side effects, event callbacks, and other functions could close over an old value, and they won&#x27;t get updated when state changes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s see how a ref can fix this code:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> count </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">current</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// or React.createRef(0);</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">makePrinter</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> printCount </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">makePrinter</span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">printCount</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// outputs 0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">count</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class=""> </span><span class="" style="color:#8B5CF6">+=</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">printCount</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// outputs 1</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now&#x27;s a good time to point out the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">const</code>. In JavaScript, &quot;constant&quot; is not the same as &quot;immutable.&quot; The ref is the same object, it&#x27;s just mutated.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With this in mind, let&#x27;s build our hook. We can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code> to keep some state around. However, we don&#x27;t want to actually <em>set</em> the state at all -- refs don&#x27;t trigger renders. Our hook can take in some default value, create an object with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.current</code>, and use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code> to persist it across renders.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">defaultValue</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">ref</span><span class="" style="color:#ff218c">,</span><span class=""> setRef</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">current</span><span class="" style="color:#8B5CF6">:</span><span class=""> defaultValue </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> ref</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef</code> can also take in a function to create the default value on demand. Thankfully, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code> allows this too. We can always call <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code> with a function argument, and in this function, we can test if the default value provided is itself a function, calling it if necessary.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">defaultValue</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">ref</span><span class="" style="color:#ff218c">,</span><span class=""> setRef</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">typeof</span><span class=""> defaultValue </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;function&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">current</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">defaultValue</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">current</span><span class="" style="color:#8B5CF6">:</span><span class=""> defaultValue </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> ref</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, let&#x27;s finish with an example:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">RefExample</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> messageRef </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useEffect</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> interval </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">setInterval</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">messageRef</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">.</span><span class="">style</span><span class="" style="color:#ff218c">.</span><span class="">backgroundColor</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;black&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        messageRef</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">.</span><span class="">style</span><span class="" style="color:#ff218c">.</span><span class="">backgroundColor</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;white&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        messageRef</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">.</span><span class="">style</span><span class="" style="color:#ff218c">.</span><span class="">backgroundColor</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;black&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">500</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">clearInterval</span><span class="" style="color:#ff218c">(</span><span class="">interval</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">messageRef</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">ref</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">messageRef</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">Hello World!</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">App</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#404040">RefExample</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="">ReactDOM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">render</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#404040">App</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">getElementById</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;root&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usereducer"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useReducer</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This hook can be thought of primarily as an alternative to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code>. A reducer is a function that takes in a state and an action and returns an updated state.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> initial </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">count</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> action</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;increment&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">count</span><span class="" style="color:#8B5CF6">:</span><span class=""> state</span><span class="" style="color:#ff218c">.</span><span class="">count</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;decrement&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">count</span><span class="" style="color:#8B5CF6">:</span><span class=""> state</span><span class="" style="color:#ff218c">.</span><span class="">count</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;reset&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">count</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">throw</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Error</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Unsupported reducer action&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can use a reducer <em>without</em> React as follows:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">let</span><span class=""> state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">...</span><span class="">initial </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// { count: 0 }</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;increment&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// { count: 1 }</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;increment&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;increment&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;decrement&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// { count: 2 }</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">state </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;reset&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// { count: 0 }</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;double&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// Error: Unsupported reducer action</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All right, let&#x27;s look back at React. Here&#x27;s how you might use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useReducer</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">ReducerExample</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> dispatch</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> initial</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class="">count is </span><span class="" style="color:#ff218c">{</span><span class="">state</span><span class="" style="color:#ff218c">.</span><span class="">count</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">dispatch</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff">&quot;increment&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">increment</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">dispatch</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff">&quot;decrement&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">decrement</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">dispatch</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff">&quot;reset&quot;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">reset</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">App</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#404040">ReducerExample</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="">ReactDOM</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">render</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#404040">App</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">getElementById</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;root&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useReducer</code> hook returns two values: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">state</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dispatch</code>. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">state</code> object is, unsurprisingly, the current state. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dispatch</code> function is a function that calls the reducer with the current state to set the new state. Notably, you don&#x27;t have to provide the current state to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dispatch</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With this in mind, we can start scaffolding the signature of our version of this hook.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> initial</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ll need to keep track of the current state somewhere. Let&#x27;s use the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useState</code> hook for this. We&#x27;ll also need to define some kind of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dispatch</code> function.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> initial</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> setState</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="">initial</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> dispatch </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useCallback</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#9CA3AF;font-style:italic">// do something with the current state</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> dispatch</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, remember that our reducer has signature <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">reducer(state, action)</code>. In our dispatch function, we can call our reducer with the current state and provided action, then set the new state to the returned value.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> initial</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> setState</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="">initial</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> dispatch </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useCallback</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">setState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> action</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> dispatch</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One more thing: React&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useReducer</code> allows passing in an initializer function as a third argument. We can match this behavior in our hook by passing a function to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">setState</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> initial</span><span class="" style="color:#ff218c">,</span><span class=""> initFunction</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> setState</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">initFunction</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">initFunction</span><span class="" style="color:#ff218c">(</span><span class="">initial</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> initial</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> dispatch </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useCallback</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="">action</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">setState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">reducer</span><span class="" style="color:#ff218c">(</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> action</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">state</span><span class="" style="color:#ff218c">,</span><span class=""> dispatch</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And use it like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">useReducer</span><span class="" style="color:#ff218c">(</span><span class="">reducer</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">count</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">{</span><span class=""> count </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usememo"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Memoization</em> refers to the practice of making a function &quot;remember&quot; its past inputs and output, so that it doesn&#x27;t need to execute again if its inputs don&#x27;t change.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Consider a function like:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;executing very expensive computation...&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#404040">Math</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">exp</span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can make a new, memoized version that &quot;remembers&quot; its past inputs and output. To associate these inputs and outputs, we can use an immediately-invoked function expression.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> memoized </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> previousInput </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> previousOutput </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">null</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;executing very expensive computation...&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#404040">Math</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">exp</span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">x </span><span class="" style="color:#8B5CF6">===</span><span class=""> previousInput</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> previousOutput</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      previousInput </span><span class="" style="color:#8B5CF6">=</span><span class=""> x</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      previousOutput </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">return</span><span class=""> previousOutput</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If we try this, we can see that the function is only evaluated when the input changes:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Not memoized&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">expensive</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 7.3891</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Memoized&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">memoized</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">memoized</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">memoized</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// 1</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">memoized</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// executing very expensive computation..., 7.3891</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is the building block we&#x27;ll need to use for our memoized function.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">React&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code> is a bit different, though. It assumes that the function <em>closes</em> over the values it needs, then uses an extra &quot;dependency array&quot; to determine when changes are necessary. Again, let&#x27;s start with its signature.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">func</span><span class="" style="color:#ff218c">,</span><span class=""> deps</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">func</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">func</code> is the function that we want to memoize, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">deps</code> is the array of dependencies. This &quot;works,&quot; but doesn&#x27;t memoize anything.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since our hook will be called on each render, we&#x27;ll need a mechanism to keep the old value around. Remember, the whole point of this is to not re-invoke things on every render. We can <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef</code> to keep things around as a ref.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">func</span><span class="" style="color:#ff218c">,</span><span class=""> deps</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> previousDeps </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class="" style="color:#ff218c">(</span><span class="">deps</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> previousValue </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class="" style="color:#ff218c">(</span><span class="">func</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">func</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef(deps)</code> returns a ref initialized to the dependency list. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef(func)</code> takes advantage of the special behavior of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef</code> when passed a function—it initializes the ref to the output of the function.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s compare the previous dependencies with the current dependencies. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Object.is()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Array.prototype.every()</code></a> are both helpful here.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">func</span><span class="" style="color:#ff218c">,</span><span class=""> deps</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> previousDeps </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class="" style="color:#ff218c">(</span><span class="">deps</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> previousValue </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useRef</span><span class="" style="color:#ff218c">(</span><span class="">func</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> matches </span><span class="" style="color:#8B5CF6">=</span><span class=""> deps</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">every</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">dep</span><span class="" style="color:#ff218c">,</span><span class=""> i</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#404040">Object</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">is</span><span class="" style="color:#ff218c">(</span><span class="">dep</span><span class="" style="color:#ff218c">,</span><span class=""> previousDeps</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">[</span><span class="">i</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">!</span><span class="">matches</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    previousValue</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">func</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> previousValue</span><span class="" style="color:#ff218c">.</span><span class="">current</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s finish this one off with an example.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">MemoExample</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">x</span><span class="" style="color:#ff218c">,</span><span class=""> setX</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">y</span><span class="" style="color:#ff218c">,</span><span class=""> setY</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> exp </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Very expensive operation...&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#404040">Math</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">exp</span><span class="" style="color:#ff218c">(</span><span class="">x</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">x</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        x is </span><span class="" style="color:#ff218c">{</span><span class="">x</span><span class="" style="color:#ff218c">}</span><span class="">, y is </span><span class="" style="color:#ff218c">{</span><span class="">y</span><span class="" style="color:#ff218c">}</span><span class="">, exponent is </span><span class="" style="color:#ff218c">{</span><span class="">exp</span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">setX</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">x </span><span class="" style="color:#8B5CF6">+</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">increment x</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">setY</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">y </span><span class="" style="color:#8B5CF6">+</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">increment y</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You should see <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Very expensive operation...</code> logged to the console whenever <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">x</code> changes, but not when <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> changes. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">exp</code> should always be kept in sync with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">x</code>, but the computation is only done when <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">x</code> changes.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="usecallback"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useCallback</code></h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This hook is a variant of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code>, but it serves a different use case. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code> is primarily used to prevent extra computation. However, we can also use it to prevent the &quot;identity&quot; of a value from changing.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Conceptually, the output of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code> changes only when the dependency array changes. This means, if the value is passed down to other components, those components will only re-render when the dependency array changes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s an example (albeit a very contrived one) of this use:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">CallbackExample</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">x</span><span class="" style="color:#ff218c">,</span><span class=""> setX</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="">y</span><span class="" style="color:#ff218c">,</span><span class=""> setY</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="">React</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">useState</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> squareX </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">setX</span><span class="" style="color:#ff218c">(</span><span class="">x </span><span class="" style="color:#8B5CF6">*</span><span class=""> x</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">[</span><span class="">x</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        x is </span><span class="" style="color:#ff218c">{</span><span class="">x</span><span class="" style="color:#ff218c">}</span><span class="">, y is </span><span class="" style="color:#ff218c">{</span><span class="">y</span><span class="" style="color:#ff218c">}</span><span class="">, exponent is </span><span class="" style="color:#ff218c">{</span><span class="">exp</span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">span</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">setX</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">x </span><span class="" style="color:#8B5CF6">+</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">increment x</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">onClick</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c;font-style:italic">setY</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">y </span><span class="" style="color:#8B5CF6">+</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">&gt;</span><span class="">increment y</span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">button</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">&lt;</span><span class="" style="color:#404040">ExpensiveComponent</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#1bb3ff;font-style:italic">squareX</span><span class="" style="color:#ff218c">=</span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">squareX</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c"> </span><span class="" style="color:#ff218c">/&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">&lt;/</span><span class="" style="color:#ff218c">div</span><span class="" style="color:#ff218c">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is mostly useful if the function goes into the dependency array of another hook somewhere else.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this case, there&#x27;s nothing expensive going on that needs memoizing--the function isn&#x27;t actually executed anyway. But syntax like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">() =&gt; () =&gt; ...</code> is difficult to read and understand. Thus, we can make a version of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code> that takes in a value directly, instead of a function that returns it.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useCallback</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">callback</span><span class="" style="color:#ff218c">,</span><span class=""> deps</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">useMemo</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> callback</span><span class="" style="color:#ff218c">,</span><span class=""> deps</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this post, we&#x27;ve walked through re-implementing a few React hooks by composing existing hooks and adding some logic. With just <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">React.useState</code>, we&#x27;ve implemented <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useRef</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useReducer</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useMemo</code>, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">useCallback</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Is this practical? Not really. But I hope it gives a sense for what React hooks are &quot;made of,&quot; and gives a few examples on how hook composition can work.</p></div>]]></description>
            <link>https://breq.dev/2022/01/19/hooks</link>
            <guid isPermaLink="false">/2022/01/19/hooks</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <pubDate>Wed, 19 Jan 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[remark-abcjs]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="abcjsContainer" style="overflow:hidden;height:171.042px"><svg xmlns:xlink="http://www.w3.org/1999/xlink" role="img" fill="currentColor" stroke="currentColor" aria-label="Sheet Music for &quot;Nokia Tune&quot;" width="770" height="171.042"><style>.abcjs-dragging-in-progress text, .abcjs-dragging-in-progress tspan {-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;}</style><title>Sheet Music for &quot;Nokia Tune&quot;</title><g><path d="M 15 89.07 L 755 89.07 L 755 89.77 L 15 89.77 z" stroke="none" fill="currentColor" class="abcjs-top-line"></path><path d="M 15 96.82 L 755 96.82 L 755 97.52 L 15 97.52 z" stroke="none" fill="currentColor"></path><path d="M 15 104.57 L 755 104.57 L 755 105.27 L 15 105.27 z" stroke="none" fill="currentColor"></path><path d="M 15 112.32 L 755 112.32 L 755 113.02 L 15 113.02 z" stroke="none" fill="currentColor"></path><path d="M 15 120.07 L 755 120.07 L 755 120.77 L 15 120.77 z" stroke="none" fill="currentColor"></path></g><path d="M 0 62 L 0 0" stroke="none" fill="none" stroke-opacity="0" fill-opacity="0" class="" data-vertical="62"></path><text stroke="none" font-size="27" font-style="normal" font-family="&quot;Times New Roman&quot;" font-weight="normal" text-decoration="none" class="" text-anchor="middle" x="385" y="49.56" data-name="title"><tspan x="385">Nokia Tune</tspan></text><g class="" fill="currentColor" stroke="none" data-name="staff-extra clef"><path data-name="clefs.G" d="M 29.689999999999998 75.257c 0.09 -0.09 0.24 -0.06 0.36 0c 0.12 0.09 0.57 0.6 0.96 1.11c 1.77 2.34 3.21 5.85 3.57 8.73c 0.21 1.56 0.03 3.27 -0.45 4.86c -0.69 2.31 -1.92 4.47 -4.23 7.44c -0.3 0.39 -0.57 0.72 -0.6 0.75c -0.03 0.06 0 0.15 0.18 0.78c 0.54 1.68 1.38 4.44 1.68 5.49l 0.09 0.42l 0.39 0c 1.47 0.09 2.76 0.51 3.96 1.29c 1.83 1.23 3.06 3.21 3.39 5.52c 0.09 0.45 0.12 1.29 0.06 1.74c -0.09 1.02 -0.33 1.83 -0.75 2.73c -0.84 1.71 -2.28 3.06 -4.02 3.72l -0.33 0.12l 0.03 1.26c 0 1.74 -0.06 3.63 -0.21 4.62c -0.45 3.06 -2.19 5.49 -4.47 6.21c -0.57 0.18 -0.9 0.21 -1.59 0.21c -0.69 0 -1.02 -0.03 -1.65 -0.21c -1.14 -0.27 -2.13 -0.84 -2.94 -1.65c -0.99 -0.99 -1.56 -2.16 -1.71 -3.54c -0.09 -0.81 0.06 -1.53 0.45 -2.13c 0.63 -0.99 1.83 -1.56 3 -1.53c 1.5 0.09 2.64 1.32 2.73 2.94c 0.06 1.47 -0.93 2.7 -2.37 2.97c -0.45 0.06 -0.84 0.03 -1.29 -0.09l -0.21 -0.09l 0.09 0.12c 0.39 0.54 0.78 0.93 1.32 1.26c 1.35 0.87 3.06 1.02 4.35 0.36c 1.44 -0.72 2.52 -2.28 2.97 -4.35c 0.15 -0.66 0.24 -1.5 0.3 -3.03c 0.03 -0.84 0.03 -2.94 0 -3c -0.03 0 -0.18 0 -0.36 0.03c -0.66 0.12 -0.99 0.12 -1.83 0.12c -1.05 0 -1.71 -0.06 -2.61 -0.3c -4.02 -0.99 -7.11 -4.35 -7.8 -8.46c -0.12 -0.66 -0.12 -0.99 -0.12 -1.83c 0 -0.84 0 -1.14 0.15 -1.92c 0.36 -2.28 1.41 -4.62 3.3 -7.29l 2.79 -3.6c 0.54 -0.66 0.96 -1.2 0.96 -1.23c 0 -0.03 -0.09 -0.33 -0.18 -0.69c -0.96 -3.21 -1.41 -5.28 -1.59 -7.68c -0.12 -1.38 -0.15 -3.09 -0.06 -3.96c 0.33 -2.67 1.38 -5.07 3.12 -7.08c 0.36 -0.42 0.99 -1.05 1.17 -1.14zm 2.01 4.71c -0.15 -0.3 -0.3 -0.54 -0.3 -0.54c -0.03 0 -0.18 0.09 -0.3 0.21c -2.4 1.74 -3.87 4.2 -4.26 7.11c -0.06 0.54 -0.06 1.41 -0.03 1.89c 0.09 1.29 0.48 3.12 1.08 5.22c 0.15 0.42 0.24 0.78 0.24 0.81c 0 0.03 0.84 -1.11 1.23 -1.68c 1.89 -2.73 2.88 -5.07 3.15 -7.53c 0.09 -0.57 0.12 -1.74 0.06 -2.37c -0.09 -1.23 -0.27 -1.92 -0.87 -3.12zm -2.94 20.7c -0.21 -0.72 -0.39 -1.32 -0.42 -1.32c 0 0 -1.2 1.47 -1.86 2.37c -2.79 3.63 -4.02 6.3 -4.35 9.3c -0.03 0.21 -0.03 0.69 -0.03 1.08c 0 0.69 0 0.75 0.06 1.11c 0.12 0.54 0.27 0.99 0.51 1.47c 0.69 1.38 1.83 2.55 3.42 3.42c 0.96 0.54 2.07 0.9 3.21 1.08c 0.78 0.12 2.04 0.12 2.94 -0.03c 0.51 -0.06 0.45 -0.03 0.42 -0.3c -0.24 -3.33 -0.72 -6.33 -1.62 -10.08c -0.09 -0.39 -0.18 -0.75 -0.18 -0.78c -0.03 -0.03 -0.42 0 -0.81 0.09c -0.9 0.18 -1.65 0.57 -2.22 1.14c -0.72 0.72 -1.08 1.65 -1.05 2.64c 0.06 0.96 0.48 1.83 1.23 2.58c 0.36 0.36 0.72 0.63 1.17 0.9c 0.33 0.18 0.36 0.21 0.42 0.33c 0.18 0.42 -0.18 0.9 -0.6 0.87c -0.18 -0.03 -0.84 -0.36 -1.26 -0.63c -0.78 -0.51 -1.38 -1.11 -1.86 -1.83c -1.77 -2.7 -0.99 -6.42 1.71 -8.19c 0.3 -0.21 0.81 -0.48 1.17 -0.63c 0.3 -0.09 1.02 -0.3 1.14 -0.3c 0.06 0 0.09 0 0.09 -0.03c 0.03 -0.03 -0.51 -1.92 -1.23 -4.26zm 3.78 7.41c -0.18 -0.03 -0.36 -0.06 -0.39 -0.06c -0.03 0 0 0.21 0.18 1.02c 0.75 3.18 1.26 6.3 1.5 9.09c 0.06 0.72 0 0.69 0.51 0.42c 0.78 -0.36 1.44 -0.96 1.98 -1.77c 1.08 -1.62 1.2 -3.69 0.3 -5.55c -0.81 -1.62 -2.31 -2.79 -4.08 -3.15z"></path></g><g class="" fill="currentColor" stroke="none" data-name="staff-extra key-signature"><path data-name="accidentals.sharp" d="M 54.781000000000006 78.227c 0.21 -0.12 0.54 -0.03 0.66 0.24c 0.06 0.12 0.06 0.21 0.06 2.31c 0 1.23 0 2.22 0.03 2.22c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.43c 0 1.32 0 2.43 0.03 2.43c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.52c 0 2.28 -0.03 2.55 -0.06 2.64c -0.21 0.36 -0.72 0.36 -0.93 0c -0.03 -0.09 -0.06 -0.33 -0.06 -2.43l 0 -2.31l -1.29 0.51l -1.26 0.51l 0 2.43c 0 2.58 0 2.52 -0.15 2.67c -0.06 0.09 -0.27 0.18 -0.36 0.18c -0.12 0 -0.33 -0.09 -0.39 -0.18c -0.15 -0.15 -0.15 -0.09 -0.15 -2.43c 0 -1.23 0 -2.22 -0.03 -2.22c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.43c 0 -1.32 0 -2.43 -0.03 -2.43c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.52c 0 -2.28 0.03 -2.55 0.06 -2.64c 0.21 -0.36 0.72 -0.36 0.93 0c 0.03 0.09 0.06 0.33 0.06 2.43l 0.03 2.31l 1.26 -0.51l 1.26 -0.51l 0 -2.43c 0 -2.28 0 -2.43 0.06 -2.55c 0.06 -0.12 0.12 -0.18 0.27 -0.24zm -0.33 10.65l 0 -2.43l -1.29 0.51l -1.26 0.51l 0 2.46l 0 2.43l 0.09 -0.03c 0.06 -0.03 0.63 -0.27 1.29 -0.51l 1.17 -0.48l 0 -2.46z"></path><path data-name="accidentals.sharp" d="M 65.031 89.852c 0.21 -0.12 0.54 -0.03 0.66 0.24c 0.06 0.12 0.06 0.21 0.06 2.31c 0 1.23 0 2.22 0.03 2.22c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.43c 0 1.32 0 2.43 0.03 2.43c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.52c 0 2.28 -0.03 2.55 -0.06 2.64c -0.21 0.36 -0.72 0.36 -0.93 0c -0.03 -0.09 -0.06 -0.33 -0.06 -2.43l 0 -2.31l -1.29 0.51l -1.26 0.51l 0 2.43c 0 2.58 0 2.52 -0.15 2.67c -0.06 0.09 -0.27 0.18 -0.36 0.18c -0.12 0 -0.33 -0.09 -0.39 -0.18c -0.15 -0.15 -0.15 -0.09 -0.15 -2.43c 0 -1.23 0 -2.22 -0.03 -2.22c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.43c 0 -1.32 0 -2.43 -0.03 -2.43c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.52c 0 -2.28 0.03 -2.55 0.06 -2.64c 0.21 -0.36 0.72 -0.36 0.93 0c 0.03 0.09 0.06 0.33 0.06 2.43l 0.03 2.31l 1.26 -0.51l 1.26 -0.51l 0 -2.43c 0 -2.28 0 -2.43 0.06 -2.55c 0.06 -0.12 0.12 -0.18 0.27 -0.24zm -0.33 10.65l 0 -2.43l -1.29 0.51l -1.26 0.51l 0 2.46l 0 2.43l 0.09 -0.03c 0.06 -0.03 0.63 -0.27 1.29 -0.51l 1.17 -0.48l 0 -2.46z"></path><path data-name="accidentals.sharp" d="M 75.281 74.352c 0.21 -0.12 0.54 -0.03 0.66 0.24c 0.06 0.12 0.06 0.21 0.06 2.31c 0 1.23 0 2.22 0.03 2.22c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.43c 0 1.32 0 2.43 0.03 2.43c 0 0 0.27 -0.12 0.6 -0.24c 0.69 -0.27 0.78 -0.3 0.96 -0.15c 0.21 0.15 0.21 0.18 0.21 1.38c 0 1.02 0 1.11 -0.06 1.2c -0.03 0.06 -0.09 0.12 -0.12 0.15c -0.06 0.03 -0.42 0.21 -0.84 0.36l -0.75 0.33l -0.03 2.52c 0 2.28 -0.03 2.55 -0.06 2.64c -0.21 0.36 -0.72 0.36 -0.93 0c -0.03 -0.09 -0.06 -0.33 -0.06 -2.43l 0 -2.31l -1.29 0.51l -1.26 0.51l 0 2.43c 0 2.58 0 2.52 -0.15 2.67c -0.06 0.09 -0.27 0.18 -0.36 0.18c -0.12 0 -0.33 -0.09 -0.39 -0.18c -0.15 -0.15 -0.15 -0.09 -0.15 -2.43c 0 -1.23 0 -2.22 -0.03 -2.22c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.43c 0 -1.32 0 -2.43 -0.03 -2.43c 0 0 -0.27 0.12 -0.6 0.24c -0.69 0.27 -0.78 0.3 -0.96 0.15c -0.21 -0.15 -0.21 -0.18 -0.21 -1.38c 0 -1.02 0 -1.11 0.06 -1.2c 0.03 -0.06 0.09 -0.12 0.12 -0.15c 0.06 -0.03 0.42 -0.21 0.84 -0.36l 0.78 -0.33l 0 -2.52c 0 -2.28 0.03 -2.55 0.06 -2.64c 0.21 -0.36 0.72 -0.36 0.93 0c 0.03 0.09 0.06 0.33 0.06 2.43l 0.03 2.31l 1.26 -0.51l 1.26 -0.51l 0 -2.43c 0 -2.28 0 -2.43 0.06 -2.55c 0.06 -0.12 0.12 -0.18 0.27 -0.24zm -0.33 10.65l 0 -2.43l -1.29 0.51l -1.26 0.51l 0 2.46l 0 2.43l 0.09 -0.03c 0.06 -0.03 0.63 -0.27 1.29 -0.51l 1.17 -0.48l 0 -2.46z"></path></g><g class="" fill="currentColor" stroke="none" data-name="staff-extra time-signature"><path data-name="3" d="M 92.611 89.947c 0.3 -0.03 1.41 0 1.83 0.06c 2.22 0.3 3.51 1.32 3.72 2.91c 0.03 0.33 0.03 1.26 -0.03 1.65c -0.12 0.84 -0.48 1.47 -1.05 1.77c -0.27 0.15 -0.36 0.24 -0.45 0.39c -0.09 0.21 -0.09 0.36 0 0.57c 0.09 0.15 0.18 0.24 0.51 0.39c 0.75 0.42 1.23 1.14 1.41 2.13c 0.06 0.42 0.06 1.35 0 1.71c -0.18 0.81 -0.48 1.38 -1.02 1.95c -0.75 0.72 -1.8 1.2 -3.18 1.38c -0.42 0.06 -1.56 0.06 -1.95 0c -1.89 -0.33 -3.18 -1.29 -3.51 -2.64c -0.03 -0.12 -0.03 -0.33 -0.03 -0.6c 0 -0.36 0 -0.42 0.06 -0.63c 0.12 -0.3 0.27 -0.51 0.51 -0.75c 0.24 -0.24 0.45 -0.39 0.75 -0.51c 0.21 -0.06 0.27 -0.06 0.6 -0.06c 0.33 0 0.39 0 0.6 0.06c 0.3 0.12 0.51 0.27 0.75 0.51c 0.36 0.33 0.57 0.75 0.6 1.2c 0 0.21 0 0.27 -0.06 0.42c -0.09 0.18 -0.12 0.24 -0.54 0.54c -0.51 0.36 -0.63 0.54 -0.6 0.87c 0.06 0.54 0.54 0.9 1.38 0.99c 0.36 0.06 0.72 0.03 0.96 -0.06c 0.81 -0.27 1.29 -1.23 1.44 -2.79c 0.03 -0.45 0.03 -1.95 -0.03 -2.37c -0.09 -0.75 -0.33 -1.23 -0.75 -1.44c -0.33 -0.18 -0.45 -0.18 -1.98 -0.18c -1.35 0 -1.41 0 -1.5 -0.06c -0.18 -0.12 -0.24 -0.39 -0.12 -0.6c 0.12 -0.15 0.15 -0.15 1.68 -0.15c 1.5 0 1.62 0 1.89 -0.15c 0.18 -0.09 0.42 -0.36 0.54 -0.57c 0.18 -0.42 0.27 -0.9 0.3 -1.95c 0.03 -1.2 -0.06 -1.8 -0.36 -2.37c -0.24 -0.48 -0.63 -0.81 -1.14 -0.96c -0.3 -0.06 -1.08 -0.06 -1.38 0.03c -0.6 0.15 -0.9 0.42 -0.96 0.84c -0.03 0.3 0.06 0.45 0.63 0.84c 0.33 0.24 0.42 0.39 0.45 0.63c 0.03 0.72 -0.57 1.5 -1.32 1.65c -1.05 0.27 -2.1 -0.57 -2.1 -1.65c 0 -0.45 0.15 -0.96 0.39 -1.38c 0.12 -0.21 0.54 -0.63 0.81 -0.81c 0.57 -0.42 1.38 -0.69 2.25 -0.81z"></path><path data-name="4" d="M 96.441 105.477c 0.27 -0.09 0.42 -0.12 0.54 -0.03c 0.09 0.06 0.15 0.21 0.15 0.3c -0.03 0.06 -1.92 2.31 -4.23 5.04c -2.31 2.73 -4.23 4.98 -4.26 5.01c -0.03 0.06 0.12 0.06 2.55 0.06l 2.61 0l 0 -2.37c 0 -2.19 0.03 -2.37 0.06 -2.46c 0.03 -0.06 0.21 -0.18 0.57 -0.42c 1.08 -0.72 1.38 -1.08 1.86 -2.16c 0.12 -0.3 0.24 -0.54 0.27 -0.57c 0.12 -0.12 0.39 -0.06 0.45 0.12c 0.06 0.09 0.06 0.57 0.06 3.96l 0 3.9l 1.08 0c 1.05 0 1.11 0 1.2 0.06c 0.24 0.15 0.24 0.54 0 0.69c -0.09 0.06 -0.15 0.06 -1.2 0.06l -1.08 0l 0 0.33c 0 0.57 0.09 1.11 0.3 1.53c 0.36 0.75 0.93 1.17 1.68 1.26c 0.3 0.03 0.39 0.09 0.39 0.3c 0 0.15 -0.03 0.18 -0.09 0.24c -0.06 0.06 -0.09 0.06 -0.48 0.06c -0.42 0 -0.69 -0.03 -2.1 -0.24c -0.9 -0.15 -1.77 -0.15 -2.67 0c -1.41 0.21 -1.68 0.24 -2.1 0.24c -0.39 0 -0.42 0 -0.48 -0.06c -0.06 -0.06 -0.06 -0.09 -0.06 -0.24c 0 -0.21 0.06 -0.27 0.36 -0.3c 0.75 -0.09 1.32 -0.51 1.68 -1.26c 0.21 -0.42 0.3 -0.96 0.3 -1.53l 0 -0.33l -2.7 0c -2.91 0 -2.85 0 -3.09 -0.15c -0.18 -0.12 -0.3 -0.39 -0.27 -0.54c 0.03 -0.06 0.18 -0.24 0.33 -0.45c 0.75 -0.9 1.59 -2.07 2.13 -3.03c 0.33 -0.54 0.84 -1.62 1.05 -2.16c 0.57 -1.41 0.84 -2.64 0.9 -4.05c 0.03 -0.63 0.06 -0.72 0.24 -0.81l 0.12 -0.06l 0.45 0.12c 0.66 0.18 1.02 0.24 1.47 0.27c 0.6 0.03 1.23 -0.09 2.01 -0.33z"></path></g><g class="" fill="currentColor" stroke="none" data-name="bar"><path d="M 114.6 120.42L 114.6 89.42L 115.2 89.42L 115.2 120.42z" data-name="bar"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="0"><path data-name="e&#x27;" d="M 131.686 62.117000000000004c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 123.6 65.82 L 137.41 65.82 L 137.41 66.52 L 123.6 66.52 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 123.6 73.57 L 137.41 73.57 L 137.41 74.27 L 123.6 74.27 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 123.6 81.32 L 137.41 81.32 L 137.41 82.02 L 123.6 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 125.6 102.98L 125.6 66.94L 126.2 66.94L 126.2 102.98z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="1"><path data-name="d&#x27;" d="M 166.84466827403048 65.992c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 158.75 73.57 L 172.56 73.57 L 172.56 74.27 L 158.75 74.27 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 158.75 81.32 L 172.56 81.32 L 172.56 82.02 L 158.75 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 160.75 102.98L 160.75 70.82L 161.35 70.82L 161.35 102.98z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="2"><path data-name="f" d="M 202.00333654806096 85.367c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 195.91 116.54L 195.91 90.71L 196.91 90.71L 196.91 116.54z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="3"><path data-name="g" d="M 251.7252020561715 81.492c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 245.64 112.67L 245.64 86.83L 246.64 86.83L 246.64 112.67z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="bar"><path d="M 295.36 120.42L 295.36 89.42L 295.96 89.42L 295.96 120.42z" data-name="bar"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="4"><path data-name="c&#x27;" d="M 312.447067564282 69.867c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 304.36 73.57 L 318.17 73.57 L 318.17 74.27 L 304.36 74.27 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 304.36 81.32 L 318.17 81.32 L 318.17 82.02 L 304.36 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 306.36 102.98L 306.36 74.69L 306.96 74.69L 306.96 102.98z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="5"><path data-name="b" d="M 347.60573583831246 73.742c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 339.52 81.32 L 353.33 81.32 L 353.33 82.02 L 339.52 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 341.52 106.79L 341.52 78.57L 342.12 78.57L 342.12 106.79z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="6"><path data-name="d" d="M 382.7644041123429 93.117c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 376.67 124.29L 376.67 98.46L 377.67 98.46L 377.67 124.29z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="7"><path data-name="e" d="M 432.48626962045347 89.242c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 426.4 120.42L 426.4 94.58L 427.4 94.58L 427.4 120.42z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="bar"><path d="M 476.12 120.42L 476.12 89.42L 476.72 89.42L 476.72 120.42z" data-name="bar"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="8"><path data-name="b" d="M 493.20813512856404 73.742c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 485.12 81.32 L 498.93 81.32 L 498.93 82.02 L 485.12 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 487.12 106.85L 487.12 78.57L 487.72 78.57L 487.72 106.85z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="9"><path data-name="a" d="M 528.3668034025945 77.617c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 520.28 81.32 L 534.09 81.32 L 534.09 82.02 L 520.28 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path><path d="M 522.28 110.66L 522.28 82.44L 522.88 82.44L 522.88 110.66z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="10"><path data-name="c" d="M 563.525471676625 96.992c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 557.44 128.17L 557.44 102.33L 558.44 102.33L 558.44 128.17z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="11"><path data-name="e" d="M 613.2473371847356 89.242c 0.36 -0.03 1.2 0 1.53 0.06c 1.17 0.24 1.89 0.84 2.16 1.83c 0.06 0.18 0.06 0.3 0.06 0.66c 0 0.45 0 0.63 -0.15 1.08c -0.66 2.04 -3.06 3.93 -5.52 4.38c -0.54 0.09 -1.44 0.09 -1.83 0.03c -1.23 -0.27 -1.98 -0.87 -2.25 -1.86c -0.06 -0.18 -0.06 -0.3 -0.06 -0.66c 0 -0.45 0 -0.63 0.15 -1.08c 0.24 -0.78 0.75 -1.53 1.44 -2.22c 1.2 -1.2 2.85 -2.01 4.47 -2.22z"></path><path d="M 607.16 120.42L 607.16 94.58L 608.16 94.58L 608.16 120.42z" class="abcjs-stem" data-name="stem"></path></g><g class="" fill="currentColor" stroke="none" data-name="bar"><path d="M 656.88 120.42L 656.88 89.42L 657.48 89.42L 657.48 120.42z" data-name="bar"></path></g><g class="" fill="currentColor" stroke="none" data-name="note" selectable="false" data-index="12"><path data-name="dots.dot" d="M 682.5692026928461 76.112c 0.09 -0.03 0.27 -0.06 0.39 -0.06c 0.96 0 1.74 0.78 1.74 1.71c 0 0.96 -0.78 1.74 -1.71 1.74c -0.96 0 -1.74 -0.78 -1.74 -1.71c 0 -0.78 0.54 -1.5 1.32 -1.68z"></path><path data-name="a" d="M 675.3192026928461 77.617c 0.06 -0.03 0.27 -0.03 0.48 -0.03c 1.05 0 1.71 0.24 2.1 0.81c 0.42 0.6 0.45 1.35 0.18 2.4c -0.42 1.59 -1.14 2.73 -2.16 3.39c -1.41 0.93 -3.18 1.44 -5.4 1.53c -1.17 0.03 -1.89 -0.21 -2.28 -0.81c -0.42 -0.6 -0.45 -1.35 -0.18 -2.4c 0.42 -1.59 1.14 -2.73 2.16 -3.39c 0.63 -0.42 1.23 -0.72 1.98 -0.96c 0.9 -0.3 1.65 -0.42 3.12 -0.54zm 1.29 0.87c -0.27 -0.09 -0.63 -0.12 -0.9 -0.03c -0.72 0.24 -1.53 0.69 -3.27 1.8c -2.34 1.5 -3.3 2.25 -3.57 2.79c -0.36 0.72 -0.06 1.5 0.66 1.77c 0.24 0.12 0.69 0.09 0.99 0c 0.84 -0.3 1.92 -0.93 4.14 -2.37c 1.62 -1.08 2.37 -1.71 2.61 -2.19c 0.36 -0.72 0.06 -1.5 -0.66 -1.77z"></path><path d="M 667.88 108.79L 667.88 82.96L 668.88 82.96L 668.88 108.79z" class="abcjs-stem" data-name="stem"></path><path d="M 665.88 81.32 L 680.25 81.32 L 680.25 82.02 L 665.88 82.02 z" stroke="none" fill="currentColor" data-name="ledger" class="abcjs-ledger"></path></g><g class="" fill="currentColor" stroke="none" data-name="bar"><path d="M 754 120.42L 754 89.42L 754.6 89.42L 754.6 120.42z" data-name="bar"></path></g><path d="M125.6 104.92 L161.35 104.92L161.35 101.05 L125.6 101.05z" stroke="none" fill="currentColor" class=""></path><path d="M306.36 104.92 L342.12 108.79L342.12 104.92 L306.36 101.05z" stroke="none" fill="currentColor" class=""></path><path d="M487.12 108.79 L522.88 112.67L522.88 108.8 L487.12 104.92z" stroke="none" fill="currentColor" class=""></path></svg></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-abcjs</code> is a <a href="https://github.com/remarkjs" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Remark</a> plugin to render sheet music written in <a href="https://abcnotation.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ABC notation</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to learn more about the <a href="https://unifiedjs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Unified</a> ecosystem by writing a plugin for it, and this seemed like an interesting challenge. I also figured I might end up using it on my site if I ever get around to posting music-related content.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The plugin looks for nodes in the syntax tree with type <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">code</code> and language <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abc</code>. This means you can write ABC notation as:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">```</span><span class="">abc</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">X: 1</span></div><div class="" style="color:#404040"><span class="">T: Nokia Tune</span></div><div class="" style="color:#404040"><span class="">M: 3/4</span></div><div class="" style="color:#404040"><span class="">L: 1/8</span></div><div class="" style="color:#404040"><span class="">K: Amaj</span></div><div class="" style="color:#404040"><span class="">| e&#x27;d&#x27; f2 g2 | c&#x27;b d2 e2 | ba c2 e2 | a6 |</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It then uses <a href="https://paulrosen.github.io/abcjs/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ABCJS</a> to render the music to an SVG, storing the result in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">data</code> property of the node so that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-rehype</code> can render it as HTML.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Works well enough that I&#x27;m using it successfully on my site. That said, compromises had to be made. ABCJS doesn&#x27;t support Node.js environments out of the box, so I had to use <a href="https://github.com/ds300/patch-package" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">patch-package</a> to manually patch it, and then use a build script to include the patched version. My patch uses <a href="https://github.com/jsdom/jsdom" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">JSDOM</a> to create a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">document</code> object for DOM manipulation.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Also, this project showed me, to put it frankly, how broken the ES module rollout has become. The UnifiedJS collective has more or less entirely switched to pure ESM packages, which can&#x27;t be <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">require()</code>&#x27;d. On the other hand, Gatsby is still purely CommonJS. As a result, any Gatsby site has to pin an old version of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code>/<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype</code> and friends. I was primarily developing this plugin for my own site, but I wanted to support the latest standards, so I used <a href="https://babeljs.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Babel</a> to transpile the ES module source to CommonJS. This added complexity to the build process, and I had to pin the CommonJS dual mode versions of all the UnifiedJS packages I depended on. This ended up being kind of the worst of both worlds.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Overall, though, I&#x27;m happy I took on this project. I ended up with something useful and I learned a lot about the inner workings of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> and the rest of the Unified ecosystem.</p></div>]]></description>
            <link>https://breq.dev/projects/remark-abcjs</link>
            <guid isPermaLink="false">/projects/remark-abcjs</guid>
            <category><![CDATA[javascript]]></category>
            <pubDate>Tue, 04 Jan 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Building a text processing pipeline with Unified]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg "><a href="https://unifiedjs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Unified</a> is a set of software packages designed to work with text data. Many projects including <a href="https://gatsbyjs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Gatsby</a> uses it to render markdown. In this post, I&#x27;ll walk through setting up a processor using Unified. We&#x27;ll start by just processing <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> files, but by the end, we&#x27;ll have a working compiler from Markdown to HTML. We&#x27;ll also write several of our own plugins for Unified.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m going to assume some basic familiarity with JavaScript and NPM, but my hope is that those new to Node or modern JavaScript will still be able to follow along. That said, the topics will gradually get more difficult as the post continues.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="setup">Setup</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unified is written as JavaScript modules intended to be run with NodeJS. Start by setting up a new <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm</code> package.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">mkdir</span><span class=""> unified-example</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#404040">cd</span><span class=""> unified-example</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> init -y</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This will generate a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">package.json</code> file for you.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Unified ecosystem uses ECMAScript modules exclusively. However, Node defaults to using CommonJS modules. We will need to modify the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">package.json</code> file to enable modules. Open up this folder in your text editor of choice, and add a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&quot;type&quot;: &quot;module&quot;</code> declaration at the end:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">[</span><span class="">...</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;type&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;module&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, we can install Unified itself.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> unified</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s create an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.js</code> file for our code.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And finally, add an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm run build</code> command to our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">package.json</code>. Modify the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&quot;scripts&quot;</code> section to add the following.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">&quot;scripts&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">&quot;build&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;node ./index.js&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    ...</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can now run our pipeline with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm run build</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And... nothing happened! That&#x27;s okay: We aren&#x27;t feeding anything to our processor yet.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="input-and-output">Input and Output</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s configure our processor to read from a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">src</code> directory, and write to a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dist</code> directory.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">mkdir</span><span class=""> src</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">mkdir</span><span class=""> dist</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we need some way to run multiple files through our pipeline. We can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unified-engine</code> for this. The engine will select all files from our source paths, run them through the processor, and output them to the destination path.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> unified-engine</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And finally, let&#x27;s use it in our code.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">try</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">engine</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        processor</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">files</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;./src/**/*.txt&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">output</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./dist&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      resolve</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">catch</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">reject</span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unified-engine</code> will call a callback function when it is finished processing all files. Unfortunately, it doesn&#x27;t support <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">await</code>-ing the result directly. So, we use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">await new Promise</code> to wait for the callback. If you aren&#x27;t familiar with Promises, you can think of this as &quot;waiting for the callback to be called&quot; instead of writing a separate callback function.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, if we run this... still nothing. We don&#x27;t have any files to process. We can make an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.txt</code> file in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">src</code> directory, and run it again:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#404040">echo</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Hello world&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> src/index.txt</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> run build</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">./src/index.txt</span></div><div class="" style="color:#404040"><span class="">  1:1  error  TypeError: Cannot `parse` without `Parser`</span></div><div class="" style="color:#404040"><span class="">    at assertParser (file:///Users/breq/code/unified-example/node_modules/unified/lib/index.js:507:11)</span></div><div class="" style="color:#404040"><span class="">    at Function.parse (file:///Users/breq/code/unified-example/node_modules/unified/lib/index.js:265:5)</span></div><div class="" style="color:#404040"><span class="">    at parse (file:///Users/breq/code/unified-example/node_modules/unified-engine/lib/file-pipeline/parse.js:50:36)</span></div><div class="" style="color:#404040"><span class="">    at wrapped (file:///Users/breq/code/unified-example/node_modules/trough/index.js:111:16)</span></div><div class="" style="color:#404040"><span class="">    at next (file:///Users/breq/code/unified-example/node_modules/trough/index.js:62:23)</span></div><div class="" style="color:#404040"><span class="">    at done (file:///Users/breq/code/unified-example/node_modules/trough/index.js:145:7)</span></div><div class="" style="color:#404040"><span class="">    at file:///Users/breq/code/unified-example/node_modules/unified-engine/lib/file-pipeline/configure.js:76:5</span></div><div class="" style="color:#404040"><span class="">    at file:///Users/breq/code/unified-example/node_modules/unified-engine/lib/configuration.js:138:11</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">✖ 1 error</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The pipeline is trying to process our input file, but it doesn&#x27;t have any parser configured.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="parsers">Parsers</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Parsers are what Unified uses to convert an input file into a syntax tree. They exist for plaintext (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> files), Markdown, and HTML.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In general, packages to work with Unified are split into three groups: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> for handling markdown, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype</code> for handling HTML, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> for handling plain text.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For our example, we&#x27;re reading in a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> file. We can use a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> plugin to convert it to a syntax tree. Currently, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> plugins are available for English and Dutch, plus a catchall <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-latin</code> plugin for languages that use Latin-based scripts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s assume we&#x27;re going to work exclusively with English.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> retext-english</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And now, we can add our parser to our pipeline.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEnglish</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-english&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEnglish</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Give it another <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm run build</code> and...</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">./src/index.txt</span></div><div class="" style="color:#404040"><span class="">  1:1  error  TypeError: Cannot `stringify` without `Compiler`</span></div><div class="" style="color:#404040"><span class="">    at assertCompiler (file:///Users/breq/code/unified-example/node_modules/unified/lib/index.js:520:11)</span></div><div class="" style="color:#404040"><span class="">    at Function.stringify (file:///Users/breq/code/unified-example/node_modules/unified/lib/index.js:281:5)</span></div><div class="" style="color:#404040"><span class="">    at stringify (file:///Users/breq/code/unified-example/node_modules/unified-engine/lib/file-pipeline/stringify.js:59:31)</span></div><div class="" style="color:#404040"><span class="">    at wrapped (file:///Users/breq/code/unified-example/node_modules/trough/index.js:111:16)</span></div><div class="" style="color:#404040"><span class="">    at next (file:///Users/breq/code/unified-example/node_modules/trough/index.js:62:23)</span></div><div class="" style="color:#404040"><span class="">    at Object.run (file:///Users/breq/code/unified-example/node_modules/trough/index.js:33:5)</span></div><div class="" style="color:#404040"><span class="">    at run (file:///Users/breq/code/unified-example/node_modules/unified-engine/lib/file-pipeline/index.js:57:10)</span></div><div class="" style="color:#404040"><span class="">    at wrapped (file:///Users/breq/code/unified-example/node_modules/trough/index.js:111:16)</span></div><div class="" style="color:#404040"><span class="">    at next (file:///Users/breq/code/unified-example/node_modules/trough/index.js:62:23)</span></div><div class="" style="color:#404040"><span class="">    at done (file:///Users/breq/code/unified-example/node_modules/trough/index.js:145:7)</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">✖ 1 error</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our pipeline is processing our file, but it can&#x27;t stringify and save the result. This is where we need a compiler.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="compilers">Compilers</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Compilers are what Unified uses to convert a syntax tree back into a file. Just like with parsers, they exist for all sorts of markup languages. For now, let&#x27;s keep things simple and output the result as a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> file.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Again, the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> ecosystem will help us. We can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-stringify</code> as our compiler to output another <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> file.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> retext-stringify</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEnglish</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-english&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextStringify</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-stringify&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEnglish</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, our pipeline runs! We now have a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dist/index.txt</code> file containing our &quot;Hello world&quot; text.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">...so what was the point of this? Right now, it seems like all we have is a complicated way to copy files between directories. But the intermediate syntax tree is where the magic happens—we can perform all sorts of processing steps on our text.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="syntax-trees">Syntax Trees</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Before diving into what syntax trees let us do, let&#x27;s take a look at what one looks like.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Syntax trees in Unified follow the <a href="https://github.com/syntax-tree/unist" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">unist</a> specification. This spec defines <em>nodes</em>, which can be either <em>parent</em> nodes (which contain other nodes) or <em>literal</em> nodes (which contain some specific value).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unist-util-inspect</code> package is a useful tool for inspecting <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unist</code> syntax trees. Let&#x27;s add it to our pipeline.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> unist-util-inspect</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using this library is a bit tricky right now. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unist-util-inspect</code> isn&#x27;t aware of any of the Unified tooling we have—it&#x27;s just a function that takes in a syntax tree. We need to hook into the pipeline somehow.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To do this, we need to write our own plugin.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="making-a-plugin">Making a Plugin</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the Unified ecosystem, a plugin is a function that takes in some options and returns another function. The returned function is then called on the syntax tree.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s write a plugin called <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">inspectPlugin</code> that logs the syntax tree to the console.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> inspect </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unist-util-inspect&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">inspectPlugin</span><span class="" style="color:#ff218c">(</span><span class="">options </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">inspect</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEnglish</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextStringify</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">inspectPlugin</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Not too bad, right? Writing our own plugin only took 5 lines of code. Now, if we run our pipeline again, we should see:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">RootNode[2] (1:1-2:1, 0-12)</span></div><div class="" style="color:#404040"><span class="">├─0 ParagraphNode[1] (1:1-1:12, 0-11)</span></div><div class="" style="color:#404040"><span class="">│   └─0 SentenceNode[3] (1:1-1:12, 0-11)</span></div><div class="" style="color:#404040"><span class="">│       ├─0 WordNode[1] (1:1-1:6, 0-5)</span></div><div class="" style="color:#404040"><span class="">│       │   └─0 TextNode &quot;Hello&quot; (1:1-1:6, 0-5)</span></div><div class="" style="color:#404040"><span class="">│       ├─1 WhiteSpaceNode &quot; &quot; (1:6-1:7, 5-6)</span></div><div class="" style="color:#404040"><span class="">│       └─2 WordNode[1] (1:7-1:12, 6-11)</span></div><div class="" style="color:#404040"><span class="">│           └─0 TextNode &quot;world&quot; (1:7-1:12, 6-11)</span></div><div class="" style="color:#404040"><span class="">└─1 WhiteSpaceNode &quot;\n&quot; (1:12-2:1, 11-12)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is the syntax tree that our pipeline built. Specifically, this is the tree that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-english</code> created, and it&#x27;s what <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-stringify</code> used to compile our output file.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="text-processing">Text Processing</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="spell-checking">Spell Checking</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are plenty of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> plugins that can work with text. Let&#x27;s start by adding spell checking to our pipeline using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-spell</code>. We also need to install a dictionary package: let&#x27;s use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dictionary-en</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> retext-spell dictionary-en</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now add</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextSpell</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-spell&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">dictionary</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;dictionary-en&quot;</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">to the imports, and add</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSpell</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> dictionary </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">to the processor. You might notice that we&#x27;re passing in an object to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.use</code>. These are the configuration options for the plugin. Most plugins take in optional options of some sort, but in this case, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-spell</code> requires the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dictionary</code> option.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Run our pipeline again, and nothing extra should happen. Let&#x27;s misspell some words and run it again!</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#404040">echo</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Ehllo world&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> src/index.txt</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> run build</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">1:1-1:6  warning  `Ehllo` is misspelt; did you mean `Hello`?  ehllo  retext-spell</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">⚠ 1 warning</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And there you have it: spell checking!</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="other-retext-plugins">Other ReText Plugins</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can pull in more plugins, too. But first, let&#x27;s get a bit more source text. I&#x27;m going to use a snippet of one of my project writeups:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">The STM32 microcontroller this project used doesn&#x27;t have any purpose-built</span></div><div class="" style="color:#404040"><span class="">hardware for generating sounds (that I&#x27;m aware of). So, the solution I</span></div><div class="" style="color:#404040"><span class="">settled on was to manually generate a square wave by setting a GPIO pin</span></div><div class="" style="color:#404040"><span class="">high, waiting for half the length of the waveform, setting it low, and</span></div><div class="" style="color:#404040"><span class="">waiting for the rest of the waveform.</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">The biggest hurdle with this approach was accurate timing. The STM32 can</span></div><div class="" style="color:#404040"><span class="">use interrupts to delay for a precise number of milliseconds, but</span></div><div class="" style="color:#404040"><span class="">generating square waves at specific frequencies requires sub-millisecond</span></div><div class="" style="color:#404040"><span class="">precision. The solution I came up with was to calibrate a busy-wait loop</span></div><div class="" style="color:#404040"><span class="">when the code begins using the millisecond timer, then use that busy-wait</span></div><div class="" style="color:#404040"><span class="">loop for sub-millisecond-precision delays. This yielded a decent-sounding</span></div><div class="" style="color:#404040"><span class="">square wave, but the game audio still felt incomplete.</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We should also probably stop logging the entire syntax tree to the console. Comment out the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">console.log</code> for now in our custom plugin.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s install some more prose plugins. I&#x27;m going to throw pretty much the entire suite of plugins into our pipeline.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> retext-contractions retext-diacritics retext-equality retext-indefinite-article retext-profanities retext-repeated-words retext-smartypants retext-quotes</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our full code should now look like:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEnglish</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-english&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextStringify</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-stringify&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> inspect </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unist-util-inspect&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextSpell</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-spell&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">dictionary</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;dictionary-en&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextContractions</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-contractions&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextDiacritics</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-diacritics&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEquality</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-equality&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextIndefiniteArticle</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-indefinite-article&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextProfanities</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-profanities&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextRepeatedWords</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-repeated-words&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextSmartypants</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-smartypants&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextQuotes</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-quotes&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">inspectPlugin</span><span class="" style="color:#ff218c">(</span><span class="">options </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#9CA3AF;font-style:italic">// console.log(inspect(tree));</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEnglish</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextStringify</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">inspectPlugin</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSpell</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> dictionary </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextContractions</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextDiacritics</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEquality</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextIndefiniteArticle</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextProfanities</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextRepeatedWords</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextQuotes</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">try</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">engine</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        processor</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">files</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;./src/**/*.txt&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">output</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./dist&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      resolve</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">catch</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">reject</span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And run! The file was written to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">./dist/index.txt</code> successfully, but there were a few warnings:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">./src/index.txt &gt; dist/index.txt</span></div><div class="" style="color:#404040"><span class="">     1:5-1:10  warning  `STM32` is misspelt; did you mean `STM32nd`?                     stm32             retext-spell</span></div><div class="" style="color:#404040"><span class="">    1:11-1:26  warning  `microcontroller` is misspelt                                    microcontroller   retext-spell</span></div><div class="" style="color:#404040"><span class="">    1:45-1:52  warning  Expected the apostrophe in `doesn&#x27;t` to be like this: `doesn’t`  smart-apostrophe  retext-contractions</span></div><div class="" style="color:#404040"><span class="">  1:113-1:116  warning  Expected the apostrophe in `I&#x27;m` to be like this: `I’m`          smart-apostrophe  retext-contractions</span></div><div class="" style="color:#404040"><span class="">  1:210-1:214  warning  `GPIO` is misspelt; did you mean `GPO`?                          gpio              retext-spell</span></div><div class="" style="color:#404040"><span class="">    3:64-3:69  warning  `STM32` is misspelt; did you mean `STM32nd`?                     stm32             retext-spell</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">⚠ 6 warnings</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A few technical words (&quot;STM32&quot;, &quot;microcontroller&quot;, &quot;GPIO&quot;) are incorrectly detected as misspelled. We can add a personal dictionary to resolve this.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#404040">echo</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;STM32</span><span class="" style="color:#1bb3ff">\n</span><span class="" style="color:#1bb3ff">microcontroller</span><span class="" style="color:#1bb3ff">\n</span><span class="" style="color:#1bb3ff">GPIO&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&gt;</span><span class=""> dictionary.txt</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we can configure <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-spell</code> to use our personal dictionary.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">fs</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;fs/promises&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> personal </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">await</span><span class=""> fs</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">readFile</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;./dictionary.txt&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;utf8&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSpell</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    dictionary</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    personal</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we only have quote errors remaining. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-contractions</code> expects us to use smart apostrophes. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code> adds those automatically. If you look at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dist/index.txt</code>, you&#x27;ll see that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">doesn&#x27;t</code> is now <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">doesn’t</code>. So why is <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-contractions</code> complaining?</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="plugin-types-and-plugin-order">Plugin Types and Plugin Order</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The issue is the order that our plugins are being used. Since <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-contractions</code> comes before <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code>, the smart quote insertion happens after the smart quotes are checked.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, you might notice that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-stringify</code> is the second plugin we use, yet the other plugins modify the tree before it is stringified and written to disk. Why?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The answer is that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-stringify</code> works a bit differently than you&#x27;d expect. Instead of performing some operation on the tree directly, it configures the processor object, setting itself as the compiler. This means that even though the plugin is one of the first in the pipeline, nothing is executed until the pipeline reaches the compile step.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s reorder our plugins. I&#x27;m going to list the parser first, then the plugins that modify the tree (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code>), then those that check the tree (including <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-contractions</code>), and finally the compiler (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-stringify</code>). Again, the parser and compiler can go anywhere in the order, but placing them at the beginning and end reduces confusion.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our pipeline should run without warnings!</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now&#x27;s a good time to stop and test out some of the plugins we&#x27;re using:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Spell something wrong for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-spell</code> to catch</li>
<li class="my-2 pl-2">Put an apostrophe in the wrong place (e.g. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">do&#x27;nt</code>)</li>
<li class="my-2 pl-2">Miss some diacritics (e.g. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">a la carte</code>)</li>
<li class="my-2 pl-2">Use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">a example</code> instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">an example</code></li>
<li class="my-2 pl-2">Repeat a word, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">The bird in the the bush</code></li>
<li class="my-2 pl-2">Use gendered language (e.g. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">postman</code>)</li>
<li class="my-2 pl-2">Use profane language (e.g. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">stupid</code>)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Unfortunately, with any large amount of text, a lot of false positives can occur. In most cases, you&#x27;ll only want to use a few of these plugins to lint your text. I&#x27;m going to remove <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-contractions</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-diacritics</code> at this step.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="markdown">Markdown</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You&#x27;ll probably want to use Markdown for any serious work. Markdown allows you to embed links, images, code blocks, and other content into your text.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is where the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> family of plugins can help. We&#x27;ll use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-parse</code> to parse our Markdown files, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-stringify</code> to convert the tree back to Markdown.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-parse remark-stringify</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="splitting-the-pipeline">Splitting the pipeline</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, our plugins are designed to work with and modify a text syntax tree. If we want to process Markdown, we&#x27;ll need some way to convert the Markdown syntax tree into a text syntax tree.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is more complicated than it sounds. We can&#x27;t use a plugin to replace the Markdown syntax tree with a text one, since we still need to output the Markdown.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What we <em>can</em> do is create a separate pipeline that only deals with prose. Let&#x27;s move our existing pipeline to a new file, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">prose.js</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEnglish</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-english&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextStringify</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-stringify&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextSpell</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-spell&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">dictionary</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;dictionary-en&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextEquality</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-equality&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextIndefiniteArticle</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-indefinite-article&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextProfanities</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-profanities&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextRepeatedWords</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-repeated-words&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextSmartypants</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-smartypants&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">retextQuotes</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;retext-quotes&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">fs</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;fs/promises&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> personal </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">await</span><span class=""> fs</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">readFile</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;./dictionary.txt&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;utf8&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Parser</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEnglish</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Transform prose</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Check prose</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextSpell</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    dictionary</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    personal</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextEquality</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextIndefiniteArticle</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextProfanities</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextRepeatedWords</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextQuotes</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#9CA3AF;font-style:italic">// Compiler</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">retextStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">export</span><span class=""> </span><span class="" style="color:#8B5CF6">default</span><span class=""> processor</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we can import this in our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.js</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">processor</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./prose.js&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">try</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">engine</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        processor</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">files</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;./src/**/*.txt&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">output</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./dist&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      resolve</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">catch</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">reject</span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="parsing-markdown">Parsing Markdown</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We need some Markdown to parse. I&#x27;m using <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/unified-example/markdown.md">this</a> as my source. Save it as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">src/index.md</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, we&#x27;ll make a new pipeline that can parse Markdown, using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-parse</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-stringify</code>. We&#x27;ll also configure our engine to look for <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.md</code> files instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.txt</code> files.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> unified </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> engine </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unified-engine&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkParse</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-parse&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkStringify</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-stringify&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Promise</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="">resolve</span><span class="" style="color:#ff218c">,</span><span class=""> reject</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">try</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">engine</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        processor</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">files</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;./src/**/*.md&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">output</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./dist&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      resolve</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">catch</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">reject</span><span class="" style="color:#ff218c">(</span><span class="">error</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, we need to actually do the split. If we want to bridge from Markdown to text, from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code>, we can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-retext</code>!</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-retext</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkRetext</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-retext&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You&#x27;ll notice that this gives an error, though. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-retext</code> plugin is looking for some <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext</code> parser to use. This is where our original prose pipeline comes in.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">prosePipeline</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;./prose.js&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And now, the pipeline should run! You&#x27;ll probably see some warnings about spelling, showing that the markdown content is getting fed through the spell checker. You might want to update the dictionary before moving on.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="mutating-markdown">Mutating Markdown</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Look at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dist/index.md</code>. What happened to the smart quotes? In the prose pipeline, we feed our text through <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code> to convert straight quotes and apostrophes to curly/smart quotes. But that isn&#x27;t being reflected in the Markdown output.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Once we split the pipeline, any changes we make to the text syntax tree won&#x27;t propagate back to the Markdown tree.</em> Splitting is a one-way process.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thankfully, we can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-smartypants</code> instead of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code> to mutate the Markdown tree.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-smartypants</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Add it to the Markdown pipeline:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkSmartypants</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-smartypants&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, remove <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code> from the prose pipeline. Run the pipeline again, and you should see smart quotes in the Markdown output. You should also see that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-quotes</code> doesn&#x27;t complain about quote usage. Since we apply <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">retext-smartypants</code> before splitting the pipeline, the changes are also reflected in the prose syntax tree.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="more-markdown-plugins">More Markdown Plugins</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s add a few more plugins to our pipeline.</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-slug</code>: Generate a slug for each heading, letting people link directly to it.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-gfm</code>: Parse GitHub-style tables.</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-frontmatter</code>: Parse YAML frontmatter.</li>
</ul></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-slug remark-gfm remark-frontmatter</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkSmartypants</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-smartypants&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkSlug</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-slug&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkGfm</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-gfm&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkFrontmatter</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-frontmatter&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkFrontmatter</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkGfm</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSlug</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Try adding some frontmatter, tables, etc. to the Markdown file.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">---</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">title</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Hello World&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">---</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> this </span><span class="" style="color:#ff218c">|</span><span class=""> is    </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#ff218c">----</span><span class=""> </span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#ff218c">-----</span><span class=""> </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> a    </span><span class="" style="color:#ff218c">|</span><span class=""> table </span><span class="" style="color:#ff218c">|</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, we&#x27;re just taking in Markdown and spitting it out. Let&#x27;s try actually rendering it to HTML.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="html">HTML</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Just as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> is used for Markdown, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype</code> is used for HTML. Instead of writing our Markdown pipeline back to Markdown, let&#x27;s transform it to HTML and write that out.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ll need <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-rehype</code> to transform Markdown to HTML, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-stringify</code> to write the HTML back to a file. We&#x27;ll also use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">vfile-rename</code> to rename the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.md</code> files to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.html</code> files.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-rehype rehype-stringify vfile-rename</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we can add these to our pipeline. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">vfile-rename</code> isn&#x27;t a proper plugin, but it only takes a bit of code to make it work.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkRehype</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-rehype&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">rehypeStringify</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;rehype-stringify&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> rename </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;vfile-rename&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkFrontmatter</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkGfm</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSlug</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRehype</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">rename</span><span class="" style="color:#ff218c">(</span><span class="">file</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">extname</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;.html&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Run the pipeline again, and you should see <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.html</code> with the output.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="document-structure">Document Structure</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You might notice that the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.html</code> doesn&#x27;t include <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;head&gt;</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;body&gt;</code>. In order to actually create a complete HTML document, we need to add those. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-document</code> can turn an HTML fragment into a full document.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> rehype-document</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Add it to the pipeline, and you should see a complete HTML document.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="title">Title</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.html</code> has a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;title&gt;</code> tag, but it&#x27;s just set to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index</code>. Ideally, we&#x27;d want to be able to set the title from the frontmatter.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There&#x27;s no existing plugin to take care of this, but we can write one ourselves. We can extract the title from the frontmatter using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-extract-frontmatter</code>, and then use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">hast-util-select</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">hast-util-from-string</code> to modify the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;title&gt;</code> tag.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> hast-util-select hast-util-from-string remark-extract-frontmatter yaml</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Adding these, our pipeline looks like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> select </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;hast-util-select&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> fromString </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;hast-util-from-string&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#1bb3ff">YAML</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;yaml&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="">remarkFrontmatter</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;remark-frontmatter&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkFrontmatter</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkExtractFrontmatter</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">yaml</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">YAML</span><span class="" style="color:#ff218c">.</span><span class="">parse</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkGfm</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSlug</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRehype</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeDocument</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">title</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> title </span><span class="" style="color:#8B5CF6">=</span><span class=""> file</span><span class="" style="color:#ff218c">.</span><span class="">data</span><span class="" style="color:#ff218c">.</span><span class="">title</span><span class=""> </span><span class="" style="color:#8B5CF6">||</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> tag </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">select</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;title&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> tree</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tag</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">fromString</span><span class="" style="color:#ff218c">(</span><span class="">tag</span><span class="" style="color:#ff218c">,</span><span class=""> title</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">rename</span><span class="" style="color:#ff218c">(</span><span class="">file</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">extname</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;.html&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Set the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">title</code> property in the frontmatter of the markdown, and check that it is updated in the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.html</code> output.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="formatting">Formatting</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Right now, the HTML output isn&#x27;t particularly readable. We can add <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-format</code> to clean things up. Alternatively, you might want to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-minify</code> to reduce the file size.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> rehype-format</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Add this to the pipeline right before the call to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.use(rehypeStringify)</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="code">Code</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Add some code to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">index.md</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">```js</span></div><div class="" style="color:#404040"><span class="">() =&gt; (tree, file) =&gt; {</span></div><div class="" style="color:#404040"><span class="">  const title = file.data.title || &quot;Untitled&quot;;</span></div><div class="" style="color:#404040"><span class="">  const tag = select(&quot;title&quot;, tree);</span></div><div class="" style="color:#404040"><span class="">  if (tag) {</span></div><div class="" style="color:#404040"><span class="">    fromString(tag, title);</span></div><div class="" style="color:#404040"><span class="">  }</span></div><div class="" style="color:#404040"><span class="">};</span></div><div class="" style="color:#404040"><span class="">```</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The HTML output is fine. The code is printed in a monospace font. However, in most cases, you&#x27;ll want to display code with syntax highlighting. The Prism library is a popular choice, and it&#x27;s supported in Unified through <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-prism</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> @mapbox/rehype-prism</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypePrism</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This won&#x27;t work on its own, however. We need to add the Prism theme to actually apply the highlighting. Thankfully, all we need to do is add the URL to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">css</code> option of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-document</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeDocument</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">title</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">css</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There it is! Language-specific code highlighting has been added to the pipeline.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="math">Math</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are a lot of cases where you might want to include math in your Markdown. To accomplish this, math is typically written using LaTeX inside of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">$</code> blocks. Here&#x27;s what it looks like:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">$$ x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Getting math to display in our pipeline takes two steps. First, when parsing Markdown, we need to add a new plugin to extract the math blocks into syntax tree nodes. Then, we need a plugin to render the LaTeX to HTML once we have our HTML syntax tree.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There are two major math libraries for the web: MathJAX and KaTeX. We&#x27;ll proceed using KaTeX, since it is more lightweight.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-math rehype-katex</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, add these to the pipeline, and add the KaTeX CSS similarly to how we added the Prism theme. The pipeline looks like this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> processor </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">unified</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkParse</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkFrontmatter</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkExtractFrontmatter</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">yaml</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">YAML</span><span class="" style="color:#ff218c">.</span><span class="">parse</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkGfm</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkMath</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSlug</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkSmartypants</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRetext</span><span class="" style="color:#ff218c">,</span><span class=""> prosePipeline</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">remarkRehype</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypePrism</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeKatex</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeDocument</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">title</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeDocument</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">title</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">css</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.15.1/katex.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> title </span><span class="" style="color:#8B5CF6">=</span><span class=""> file</span><span class="" style="color:#ff218c">.</span><span class="">data</span><span class="" style="color:#ff218c">.</span><span class="">title</span><span class=""> </span><span class="" style="color:#8B5CF6">||</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">const</span><span class=""> tag </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">select</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;title&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> tree</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tag</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c;font-style:italic">fromString</span><span class="" style="color:#ff218c">(</span><span class="">tag</span><span class="" style="color:#ff218c">,</span><span class=""> title</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">rename</span><span class="" style="color:#ff218c">(</span><span class="">file</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">extname</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;.html&quot;</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeFormat</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeStringify</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Both <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-math</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rehype-katex</code> support both inline and block mode math. Inline mode can be written using a single <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">$</code> as a delimiter, and block mode uses two <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">$$</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="music">Music</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You also might want to include sheet music notation in your Markdown. The most popular notation for embedding music notation in websites is <a href="https://abcnotation.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ABC</a>. There aren&#x27;t any working <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark</code> libraries for this, but we can write our own.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For syntax, let&#x27;s use three backticks like a code block, and set the language to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abc</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">```abc</span></div><div class="" style="color:#404040"><span class="">X: 1</span></div><div class="" style="color:#404040"><span class="">T: Nokia Tune</span></div><div class="" style="color:#404040"><span class="">M: 3/4</span></div><div class="" style="color:#404040"><span class="">L: 1/8</span></div><div class="" style="color:#404040"><span class="">K: Amaj</span></div><div class="" style="color:#404040"><span class="">| e&#x27;d&#x27; f2 g2 | c&#x27;b d2 e2 | ba c2 e2 | a6 |</span></div><div class="" style="color:#404040"><span class="">```</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we can start writing our plugin. Create a new file, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">music.js</code>:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">export</span><span class=""> </span><span class="" style="color:#8B5CF6">default</span><span class=""> remarkMusic</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Import the plugin and add it to the pipeline in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">main.js</code>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The next step is to select the music nodes in the syntax tree. Let&#x27;s start by just <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">inspect</code>ing the whole tree, to get a sense of what we&#x27;re looking for.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> inspect </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unist-util-inspect&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#404040">console</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">log</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c;font-style:italic">inspect</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">export</span><span class=""> </span><span class="" style="color:#8B5CF6">default</span><span class=""> remarkMusic</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">...</span></div><div class="" style="color:#404040"><span class="">└─10 code &quot;X: 1\nT: Nokia Tune\nM: 3/4\nL: 1/8\nK: Amaj\n| e&#x27;d&#x27; f2 g2 | c&#x27;b d2 e2 | ba c2 e2 | a6 |&quot; (42:1-49:4, 1623-1717)</span></div><div class="" style="color:#404040"><span class="">      lang: &quot;abc&quot;</span></div><div class="" style="color:#404040"><span class="">      meta: null</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All right, our music is in the syntax tree, in a node with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">type=&quot;code&quot;</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">lang=&quot;abc&quot;</code>. Let&#x27;s start by mapping the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">code</code> nodes to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abc</code> nodes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To perform this mapping, we can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">unist-util-map</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> unist-util-map</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> map </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;unist-util-map&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;code&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">lang</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">value</span><span class="" style="color:#8B5CF6">:</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> node</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you run the pipeline now, you&#x27;ll see that the ABC source is now just kind of dropped into the HTML. Unsurprisingly, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-rehype</code> has no idea what to do with it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If, however, we add a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">data</code> field to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abc</code> nodes we create, we will be able to pass an HTML syntax tree node to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-rehype</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;code&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">lang</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">value</span><span class="" style="color:#8B5CF6">:</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">data</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hName</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;div&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hProperties</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">              </span><span class="" style="color:#8B5CF6">className</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">              </span><span class="" style="color:#8B5CF6">style</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;color: red&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hChildren</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">              </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">                </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;text&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">                </span><span class="" style="color:#8B5CF6">value</span><span class="" style="color:#8B5CF6">:</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">              </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> node</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this example, we just create a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">div</code> for the ABC source, and show it in red.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, if we want to actually render ABC to HTML, we&#x27;ll need to use a library that works with the DOM. Let&#x27;s get things set up beforehand by creating a DOM element and converting it to an AST node.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Since we&#x27;re working with Node, we don&#x27;t have access to a global <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">document</code> object from which to call <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">createElement</code>. Instead, we can use <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">JSDOM</code>. We&#x27;ll also need <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">hast-util-from-dom</code> to convert the DOM node to an AST node.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> jsdom hast-util-from-dom</span></div></pre></div>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#1bb3ff">JSDOM</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;jsdom&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> fromDom </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;hast-util-from-dom&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;code&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">lang</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">window</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">JSDOM</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> renderInto </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">createElement</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;div&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        renderInto</span><span class="" style="color:#ff218c">.</span><span class="">innerHTML</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        renderInto</span><span class="" style="color:#ff218c">.</span><span class="">style</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;red&quot;</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> data </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">fromDom</span><span class="" style="color:#ff218c">(</span><span class="">renderInto</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">value</span><span class="" style="color:#8B5CF6">:</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">data</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hName</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">tagName</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hProperties</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">properties</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hChildren</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">children</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> node</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">// ...</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">All right! We&#x27;ve done almost everything we need, all that&#x27;s left is to map some ABC source to an HTML DOM node. Thankfully, considering the popularity of ABC notation, there&#x27;s a library for that: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abcjs</code>.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> abcjs</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we just tell <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abcjs</code> to render into our <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">JSDOM</code> node.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">remarkMusic</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> file</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">map</span><span class="" style="color:#ff218c">(</span><span class="">tree</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">node</span><span class="" style="color:#ff218c">.</span><span class="">type</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;code&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;&amp;</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">lang</span><span class=""> </span><span class="" style="color:#8B5CF6">===</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">window</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">JSDOM</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> renderInto </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">document</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">createElement</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;div&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#1bb3ff">ABCJS</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">renderAbc</span><span class="" style="color:#ff218c">(</span><span class="">renderInto</span><span class="" style="color:#ff218c">,</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">const</span><span class=""> data </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">fromDom</span><span class="" style="color:#ff218c">(</span><span class="">renderInto</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">type</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;abc&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">value</span><span class="" style="color:#8B5CF6">:</span><span class=""> node</span><span class="" style="color:#ff218c">.</span><span class="">value</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#8B5CF6">data</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hName</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">tagName</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hProperties</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">properties</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">hChildren</span><span class="" style="color:#8B5CF6">:</span><span class=""> data</span><span class="" style="color:#ff218c">.</span><span class="">children</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">          </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">return</span><span class=""> node</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you&#x27;re following along, you might get an error after this step. ABCJS relied in the global <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">window</code> object, which doesn&#x27;t exist in Node, up until <a href="https://github.com/paulrosen/abcjs/commit/a2f7aa3d1e56129c50bda055ac5b1d70eb99d39d#diff-0396bfd9215f5827170dbea5ff6109b231a55af9bab8c7a2a094aae98da55fc0" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this commit</a>. At time of writing, it hasn&#x27;t made it into a proper release yet. As a workaround, you can install the 6.0.0 beta:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> abcjs@^6.0.0-beta</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And... still doesn&#x27;t work. When rendering to a DOM node, ABCJS tries to call <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">document.createElement</code>, which (obviously) fails. We will need to patch the package manually.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> patch-package</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I added the following patch:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">diff --git a/node_modules/abcjs/src/write/svg.js b/node_modules/abcjs/src/write/svg.js</span></div><div class="" style="color:#404040"><span class="">index 174602b..fae9221 100644</span></div><div class="" style="color:#404040"><span class=""></span><span class="">--- a/node_modules/abcjs/src/write/svg.js</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">+++ b/node_modules/abcjs/src/write/svg.js</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">@@ -2,6 +2,9 @@</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">/*global module */</span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic">const JSDOM = require(&quot;jsdom&quot;).JSDOM;</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic">const document = (new JSDOM()).window.document;</span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class="" style="color:#1bb3ff;font-style:italic">+</span><span class="" style="color:#1bb3ff;font-style:italic"></span></div><div class="" style="color:#404040"><span class="" style="color:#1bb3ff;font-style:italic"></span><span class=""> </span><span class="">var svgNS = &quot;http://www.w3.org/2000/svg&quot;;</span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""> </span><span class="">function Svg(wrapper) {</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And... there we go! Runs without issue, and transforms our ABC source into beautiful sheet music.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pulling-it-all-together">Pulling it all together</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s throw a few more styles in, just to make things look a bit nicer.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">use</span><span class="" style="color:#ff218c">(</span><span class="">rehypeDocument</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">title</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Untitled&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">css</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.15.1/katex.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.1/css/bootstrap.min.css&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">style</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;body { margin: 0 auto !important; max-width: 800px; }&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And finally, let&#x27;s throw some markdown at this! Here&#x27;s a snippet that includes basically everything we&#x27;re doing:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">---</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">title</span><span class="" style="color:#ff218c">:</span><span class=""> My Awesome Markdown</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">---</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">#</span><span class=""> Hello, World!</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">This is a Markdown document. It&#x27;s a good test for our pipeline. I hope I </span><span class="" style="color:#ff218c">~~</span><span class="">spellled</span><span class="" style="color:#ff218c">~~</span><span class=""> everything right. If someone finds a spelling </span><span class="" style="color:#ff218c">**</span><span class="">error</span><span class="" style="color:#ff218c">**</span><span class="">, she should let me </span><span class="" style="color:#1bb3ff">[</span><span class="" style="color:#1bb3ff">know</span><span class="" style="color:#1bb3ff">](</span><span class="" style="color:#1bb3ff">mailto:breq@breq.dev</span><span class="" style="color:#1bb3ff">)</span><span class="">.</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">-</span><span class=""> This is a list item</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">-</span><span class=""> This is another list item</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">1.</span><span class=""> This is a numbered list item</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">1.</span><span class=""> This is another numbered list item</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">##</span><span class=""> Tables</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> $x$ </span><span class="" style="color:#ff218c">|</span><span class=""> $x^2$ </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#ff218c">---</span><span class=""> </span><span class="" style="color:#ff218c">|</span><span class=""> </span><span class="" style="color:#ff218c">-----</span><span class=""> </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> 1   </span><span class="" style="color:#ff218c">|</span><span class=""> 1     </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> 2   </span><span class="" style="color:#ff218c">|</span><span class=""> 4     </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">|</span><span class=""> 3   </span><span class="" style="color:#ff218c">|</span><span class=""> 9     </span><span class="" style="color:#ff218c">|</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">##</span><span class=""> Formulas</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">$$</span></div><div class="" style="color:#404040"><span class="">x! = \begin{cases}</span></div><div class="" style="color:#404040"><span class="">  x = 0: &amp; 1 \\</span></div><div class="" style="color:#404040"><span class="">  x &gt; 0: &amp; x (x - 1)! \\</span></div><div class="" style="color:#404040"><span class="">\end{cases}</span></div><div class="" style="color:#404040"><span class="">$$</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">$$</span></div><div class="" style="color:#404040"><span class="">x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}</span></div><div class="" style="color:#404040"><span class="">$$</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">##</span><span class=""> Code</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span><span class="">python</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">def bisect(f, a, b):</span></div><div class="" style="color:#404040"><span class="">  c = (a + b) / 2</span></div><div class="" style="color:#404040"><span class="">  if f(c) == 0:</span></div><div class="" style="color:#404040"><span class="">    return c</span></div><div class="" style="color:#404040"><span class="">  elif f(a) * f(c) &lt; 0:</span></div><div class="" style="color:#404040"><span class="">    return bisect(f, a, c)</span></div><div class="" style="color:#404040"><span class="">  else:</span></div><div class="" style="color:#404040"><span class="">    return bisect(f, c, b)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span><span class="">jsx</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">export default function Home() {</span></div><div class="" style="color:#404040"><span class="">  return &lt;span&gt;Hello, world!&lt;/span&gt;;</span></div><div class="" style="color:#404040"><span class="">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">##</span><span class=""> Music</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span><span class="">abc</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="">X: 1</span></div><div class="" style="color:#404040"><span class="">T: Nokia Tune</span></div><div class="" style="color:#404040"><span class="">M: 3/4</span></div><div class="" style="color:#404040"><span class="">L: 1/8</span></div><div class="" style="color:#404040"><span class="">K: Amaj</span></div><div class="" style="color:#404040"><span class="">| e&#x27;d&#x27; f2 g2 | c&#x27;b d2 e2 | ba c2 e2 | a6 |</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">```</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And, let&#x27;s render it one last time.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1109" height="1196" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Funified-example.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Funified-example.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Funified-example.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Looks nice! And we&#x27;ll check the warnings:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">./src/index.md &gt; dist/index.html</span></div><div class="" style="color:#404040"><span class="">   8:3-8:11  warning  `spellled` is misspelt; did you mean `spelled`?     spellled  retext-spell</span></div><div class="" style="color:#404040"><span class="">  8:71-8:74  warning  `she` may be insensitive, use `they`, `it` instead  he-she    retext-equality</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">⚠ 2 warnings</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The pipeline is warning us about both the spelling error and the unnecessary use of gendered language.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="summary">Summary</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;ve built a pipeline that, ultimately, turns Markdown into HTML. But we&#x27;ve used the Unified ecosystem to add plenty of other features:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Use YAML frontmatter to set the page title</li>
<li class="my-2 pl-2">Use Github-Flavored Markdown to render tables, strikethroughs, and other features absent from CommonMark (the commonly used Markdown spec).</li>
<li class="my-2 pl-2">Parse ABC notation and render sheet music</li>
<li class="my-2 pl-2">Add syntax highlighting to code blocks</li>
<li class="my-2 pl-2">Render LaTeX math formulas</li>
<li class="my-2 pl-2">Add slugs to headings to support direct linking</li>
<li class="my-2 pl-2">Convert simple/straight quotes into smart quotes</li>
<li class="my-2 pl-2">Pretty-print the output HTML</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As well as our prose pipeline, which checks the source text for:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Spelling mistakes, including use of a personal dictionary to ignore certain words</li>
<li class="my-2 pl-2">Potentially insensitive or inconsiderate language (such as gendered pronouns)</li>
<li class="my-2 pl-2">Improper use of &quot;a&quot; versus &quot;an&quot;</li>
<li class="my-2 pl-2">Potentially profane language</li>
<li class="my-2 pl-2">Words that are are improperly repeated</li>
<li class="my-2 pl-2">Mistakes with quote usage</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is quite the feature set! And it goes to show just how broad the Unified ecosystem is. Most of these plugins could be added to the pipeline with just one line of code.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can see the final result <a href="https://github.com/breqdev/unified-example" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">in this repo</a>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="epilogue">Epilogue</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One final note: over the course of writing this, I actually decided to publish the music plugin to NPM. It&#x27;s available as <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">remark-abcjs</code>, and it bundles a patched version of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">abcjs</code> to avoid having to patch it yourself. Give it a try!</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">npm</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">install</span><span class=""> remark-abcjs</span></div></pre></div></div>]]></description>
            <link>https://breq.dev/2022/01/03/unified</link>
            <guid isPermaLink="false">/2022/01/03/unified</guid>
            <category><![CDATA[javascript]]></category>
            <pubDate>Mon, 03 Jan 2022 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[LetMeIn]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/zk6Bb-aY_Yo/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/zk6Bb-aY_Yo/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A video of me unlocking my door. Don&#x27;t worry, I changed my PIN afterwards.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to learn how to use <a href="https://pptr.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Puppeteer</a>, since I&#x27;ve seen it used in different projects. Puppeteer automates a browser, and it&#x27;s used for both automated testing and in various backend applications. For instance, <a href="https://www.youtube.com/watch?v=bfd8RyAJh6c" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">VSinder</a> used Puppeteer to automate screenshotting code snippets, which famously knocked <a href="https://carbon.now.sh/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">carbon</a> offline for while when it was DDOSed. Lore aside, I wanted to get experience with actually using it, since I figured it would likely come in handy at some point.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The code is just a hundred lines of NodeJS. The Puppeteer API makes heavy use of Promises, so I wrote it all using async functions. I&#x27;m using <a href="https://koajs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Koa</a> to trigger the process when a HTTP request comes in—it&#x27;s like Express, but based on promises instead of callbacks.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had a tough time getting a consistent setup to automate. Northeastern uses its own SSO system, plus <a href="https://duo.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Duo</a> for two-factor authentication. Annoyingly, these two are seemingly on different expiration timers, making reliable execution difficult.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I eventually decided to try removing all Northeastern cookies on each invocation and manually stepping through the sign-in process (typing in username and password), which worked well to consistently get through Northeastern&#x27;s login step. For Duo, I tried to save cookies between invocations, but ran into unreliable behavior.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I could have configured Duo to send an SMS to a <a href="https://www.twilio.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Twilio</a> phone number and then read that into Puppeteer to enter the 2FA code, but I didn&#x27;t want to spend money on this project. Thus, in the final video, I manually clicked &quot;approve&quot; on my phone. Sorry for the deception.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I mean, it worked? It did unlock my door. That said, it&#x27;s so impractical in its current state that I don&#x27;t think I could salvage it into something actually useful. A couple key takeaways:</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Browsers are made for humans, so they aren&#x27;t deterministic.</strong> Browsers have functionality designed for humans, and even with puppeteer, there are going to be some hiccups when controlling them with automation. Even simple things like waiting for a page to be loaded can be hard—requests happen in an unpredictable order, so waiting for one specific event or request can introduce race conditions. The recommended solution is to wait a predetermined interval after the last web request is closed, which covers <em>most</em> edge cases but is nonetheless inefficient.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Browsers are <em>huge</em>, and that leads to a lot of overhead.</strong> Including a full Chromium instance in a project makes <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">node_modules</code> massive, and even spinning up a new tab to handle a request takes an appreciable amount of time. This is a setup that could work in a parallelized testing rig, but it&#x27;s really an option of last resort for any production use case.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Not everything lends itself to automation.</strong> In this case, my script needed to wait for many different Northeastern sites to load, and considering the primary web portal I used has seemingly not been touched in a decade, it loaded quite slowly. Even though the process was automated, it wasn&#x27;t actually that much faster than doing the steps by hand. This surprised me. I&#x27;m used to humans slowing down machines, so I figured I could get a significant speedup by applying automation to the problem, but the human was never the bottleneck in the first place.</p></div>]]></description>
            <link>https://breq.dev/projects/letmein</link>
            <guid isPermaLink="false">/projects/letmein</guid>
            <category><![CDATA[puppeteer]]></category>
            <category><![CDATA[node]]></category>
            <pubDate>Sun, 19 Dec 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[flowspace]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2952" height="1670" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fflowspace%2Flogin.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fflowspace%2Flogin.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The login screen for the flowspace web app.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">flowspace is a social network website. It has a few basic features, such as direct messaging, friend requests, and public and private posts. Notably, there are two &quot;tiers&quot; of friends--the &quot;wave&quot; tier includes anyone you acknowledge and allow to message you, and the &quot;follow&quot; tier puts that person&#x27;s posts into your feed. This outer tier maintains a gate around private messaging, helping to reduce the potential for harassment, but it also doesn&#x27;t require someone to be shown every post from someone just to exchange messages.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1868" height="1284" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fflowspace%2Ffeed.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fflowspace%2Ffeed.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fflowspace%2Ffeed.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The primary feed, containing posts of people you follow.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Honestly, I kind of just wanted to take on a big project to learn and have fun. I definitely didn&#x27;t have any hopes of actually building a userbase, considering the (aptly-named) network effect would make it pretty difficult to get anything off the ground. I figured a larger web project like this would involve a lot of interesting architectural decisions and using frameworks and services that I hadn&#x27;t used much before.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The service uses a JAMstack architecture, separating the static client app from the dynamic REST API. The API backend is written in NodeJS.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Handling HTTP requests, middleware, routing, etc. is done with <a href="https://koajs.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Koa</a> and many plugins. Koa is a rewrite of the popular Express framework that uses Promises instead of callbacks--this results in much cleaner code in my opinion :)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The service uses three databases: PostgreSQL, S3 (compatible), and Redis. PostgreSQL is the primary data store, and it stores user profiles, messages, friend requests, posts, and the like. I&#x27;m using <a href="https://www.prisma.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Prisma</a> to make working with relational data easier. The S3 compatible service stores profile pictures, and it could be used to store message and post attachments as well. Initially, I used a self-hosted <a href="https://min.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Minio</a> container for this, but I decided to switch to GCP because of the generous free tier. Finally, Redis is used to handle rate-limiting the API.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The client app is a static single-page-application built with React and create-react-app. The CSS is all done in <a href="https://tailwindcss.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Tailwind</a>. I&#x27;ve deployed it to <a href="https://pages.cloudflare.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Cloudflare Pages</a>, again due to their generous free tier.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Authentication is handled through JWTs stored in localStorage. Upon login, a user will get two tokens: an access token and a refresh token. The refresh token will allow generating a new access token for up to seven days, letting users stay logged in for a while. Tokens are signed using both a secret key and the user&#x27;s password hash, ensuring that if a user resets their password, any existing tokens will be automatically invalidated. Password resets and email verification is handled through <a href="https://sendgrid.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">SendGrid</a>. (Gotta love free tiers, amirite?)</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2432" height="1484" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fflowspace%2Fmessages.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fflowspace%2Fmessages.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The messages page.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In addition to the REST API, a WebSocket &quot;Gateway&quot; endpoint is provided. This allows clients to subscribe to any message channel for updates, and receive any messages that come in on that channel. This is used for real-time direct messaging.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It works! And while it&#x27;s far from complete, the feature set is pretty good. I don&#x27;t think I&#x27;m going to work more on it, considering it&#x27;s first and foremost a learning project for myself and nobody else is using it much.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2292" height="1354" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fflowspace%2Fprofile.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fflowspace%2Fprofile.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I hadn&#x27;t worked with NodeJS in the backend before. Previously, my go-to was always Flask for these kinds of APIs, and I&#x27;d been tinkering with <a href="https://pgjones.gitlab.io/quart/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Quart</a> as well (which is a reimplementation of Flask that uses an event loop via Python&#x27;s asyncio library). Eventually, I asked myself why I was using this small fork of Flask just for the event loop feature when NodeJS was famous for its event loop and I was already familiar with JavaScript on the client side. I wasn&#x27;t sure how long it would take to get up to speed with NodeJS, but I found it pretty easy to learn from a client-side background. (Having Node APIs not match Web APIs was a bit jarring, and I&#x27;d like to look into <a href="https://deno.land/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Deno</a> at some point, but it just doesn&#x27;t seem like the ecosystem is there yet.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Working with a big relational database was also really interesting. The app has different relationship types: For instance, the mapping from users to posts is one-to-many, but the mapping of users to followers/friends is many-to-many. Learning how to express these relationship types in SQL was a surprising challenge for me, considering I hadn&#x27;t really worked with data like this before.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was also one of the first major sites I built with Tailwind. CSS is something I&#x27;m gradually starting to get better at, and I think Tailwind is helping with that. It really helped shift my perspective on CSS from &quot;those weird layout commands&quot; to &quot;nuanced rules to declaratively describe the layout of any element as a function of its size, children, etc.&quot; Miriam Suzanne&#x27;s <a href="https://www.youtube.com/watch?v=aHUtMbJw8iA" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">video</a> on &quot;Why is CSS so weird?&quot; is a really great starting point for this way of thinking, but I think it really takes time and practice to understand how to work within the CSS rules, instead of around them.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">P.S. if you&#x27;d like to message me on there, make an account and browse to my profile at <a href="https://flowspace.breq.dev/u/AtACdumJQAA" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">https://flowspace.breq.dev/u/AtACdumJQAA</a>. I might not get your message unless you ping me elsewhere though, since I never got around to implementing push notifications :)</p></div>]]></description>
            <link>https://breq.dev/projects/flowspace</link>
            <guid isPermaLink="false">/projects/flowspace</guid>
            <category><![CDATA[node]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[sql]]></category>
            <pubDate>Thu, 25 Nov 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Tunneling, Routing, and NATting my way to smuggle IPv6 into Northeastern]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Northeastern&#x27;s dorms have good WiFi... for most people. The speed is excellent, the coverage is great, and I have yet to encounter any content blocking. That said, for a tinkerer like me, I had some concerns.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="client-isolation">Client Isolation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By default, the Northeastern network isolates devices from each other. On the surface, this is a pretty smart thing to do. It&#x27;s great that my computer isn&#x27;t directly accessible from every other machine on campus.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, it can be quite a headache to work around sometimes. For my <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/wallmatrix">wall mounted matrix display</a> project, when I was at home, I would connect to it by typing <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">http://matrix.local/</code> into my browser, and <a href="https://en.wikipedia.org/wiki/Multicast_DNS" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">mDNS</a> would do its thing, connecting me directly to the display.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Obviously, with client isolation, that wasn&#x27;t going to work.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In a previous post, I wrote about the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/2021/02/10/dokku#wireguard">Wireguard server</a> that I run for myself. After setting up a Wireguard client on the matrix, I was able to access it from any of my other devices, provided I remember to turn the VPN on.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hey-if-i-have-a-vpn-cant-i-run-ipv6-through-it">Hey, if I have a VPN, can&#x27;t I run IPv6 through it?</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Northeastern&#x27;s WiFi doesn&#x27;t natively support IPv6. I want to have access to an IPv6 network, so that I can test the IPv6 functionality of my websites.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2332" height="1279" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fipv6%2Fipv6-test-neu.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fipv6%2Fipv6-test-neu.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Nope. No IPv6 support here.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, my next question was: could I route IPv6 through my VPN?</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="getting-ipv6-to-microsoft-azure">Getting IPv6 to Microsoft Azure</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I first set up my Dokku machine, IPv6 wasn&#x27;t available for VMs. It wasn&#x27;t released until <a href="https://azure.microsoft.com/en-us/updates/ipv6-for-azure-virtual-network-is-now-generally-available-2/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">April 2020</a>. Ugh.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This <a href="https://serverfault.com/questions/1014465/adding-a-public-ipv6-address-to-a-linux-vm-in-azure" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">ServerFault answer</a> helped me figure it out, but basically, I needed to attach a completely new virtual NIC to the machine. Which meant a reboot... which meant downtime... which sucks, but it&#x27;s kind of inevitable, since I&#x27;m hosting so many services on just one machine. (What can I say--as much as I&#x27;d love my own redundant Kubernetes cluster, that stuff&#x27;s <em>expensive</em> to run.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After changing the VM settings, NIC settings, VLAN settings, and VLAN subnet settings in the Azure portal, I was finally able to access the IPv6 internet from my Azure VM with its new IPv6 address.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">...its single IPv6 address.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Yep, instead of assigning a /64 to my machine, Microsoft gave me just a single address.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="fine-ill-nat-it">Fine, I&#x27;ll NAT it</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The whole point of IPv6 was to increase the available address space, removing the need for one-to-many network address translation (NAT). With IPv4, there are only enough IP addresses to give each household or small office a single address to share between all devices. Although larger businesses can get larger blocks, they still need to share. NAT allows this sharing to take place.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">IPv6 was <em>supposed</em> to remove this requirement entirely. Each subnet <em>should</em> be a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/64</code>, giving plenty of potential addresses to any connected clients. Then, end-users <em>should</em> get blocks of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/56</code> so that they can create separate subnets as needed.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">But, unfortunately, we don&#x27;t live in that reality, so I had to resort to NAT.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="docker">Docker</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Docker can be configured to support IPv6 by editing the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/etc/docker/daemon.json</code> file:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;ipv6&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">true</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">&quot;fixed-cidr-v6&quot;</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;fd16:42d4:7eff::/80&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fixed-cidr-v6</code> is <em>supposed</em> to be the public IPv6 range to assign to Docker containers. However, since I don&#x27;t have a range of addresses, I&#x27;m using a <a href="https://en.wikipedia.org/wiki/Unique_local_address" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Unique Local Address</a>, or ULA, range instead. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">42d4:73ff</code> part was randomly generated to reduce the chance of collision.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, I needed to configure Docker to NAT this ULA range instead of trying to route it directly to the Internet. I found some code (ironically itself distributed as a Docker container) that sets up this NAT: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">robbertkl</code>&#x27;s <a href="https://github.com/robbertkl/docker-ipv6nat" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">docker-ipv6nat</a>.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m aware NAT on IPv6 is almost always a no-go, since the huge number of available addresses removes the need for it. [...] I&#x27;m in no way &quot;pro IPv6 NAT&quot; in the general case; I&#x27;m just &quot;pro working shit&quot;.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At least the repo is clear about being an ugly hack. It was easy enough to deploy to Dokku with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">docker-options add deploy --privileged --network host -v /var/run/docker.sock:/var/run/docker.sock:ro -v /lib/modules:/lib/modules:ro --restart=on-failure:10</code>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wireguard-server">Wireguard Server</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, I could access the IPv6 Internet from the Wireguard container itself, but I needed to expose this to the clients. I decided to just add another NAT so that I didn&#x27;t interfere with how Docker keeps track of container IPs.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For this subnet, I chose a ULA prefix of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fd16:4252:4551::/64</code>, because I wanted something memorable, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x42 0x52 0x45 0x51</code> is the ASCII for &quot;BREQ&quot;. It might not be RFC compliant, but at least it&#x27;s cool.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by adding <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--sysctl=net.ipv6.conf.all.forwarding=1</code> to the docker options for the Wireguard container. I then added some additional <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">iptables</code> rules for the IPv6 traffic. Honestly, I&#x27;m no expert with iptables, so I just copied the default <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">iptables</code> rules but changed the command to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ip6tables</code>.</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">Rule</th><th class="border border-black p-2 dark:border-white">Explanation</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ip6tables -A FORWARD -i %i -j ACCEPT</code></td><td class="border border-gray-500 p-2">Forward all traffic inbound to the Wireguard interface (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">wg0</code>)</td></tr><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ip6tables -A FORWARD -o %i -j ACCEPT</code></td><td class="border border-gray-500 p-2">Forward all outbound traffic from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">wg0</code></td></tr><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE</code></td><td class="border border-gray-500 p-2">Apply many-to-one NAT to traffic exiting the container</td></tr></tbody></table>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wireguard-peers">Wireguard Peers</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For every peer on the network, I needed to assign an IPv6 address on the subnet. The server became <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fd16:4252:4551::1</code>, my desktop was <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">fd16:4252:4551::2</code>, and so on. I then added <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AllowedIPs=::/0</code> to each peer so that it routed all IPv6 traffic over the VPN.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s a pretty janky IPv6 implementation, especially since it has <em>two layers of NAT</em> when IPv6 wasn&#x27;t meant to be used with NAT at all, but it works!</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2344" height="1302" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fipv6%2Fipv6-test-wireguard.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fipv6%2Fipv6-test-wireguard.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As a side effect, after adding an <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">AAAA</code> record to my DNS provider, all of my API services are now available over IPv6 as well. That means <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">cards.api.breq.dev</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">botbuilder.api.breq.dev</code>, etc, are all dual-stack all the way.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a lot of effort for, well, not a lot of importance. That said, I&#x27;m glad that I got to experience some of the issues with upgrading a complex network to IPv6. In many ways, it&#x27;s a completely different mindset--under a well-thought-out IPv6 system, you want to make sure each node that <em>might have a subnet behind it at some point</em> has its own <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">/64</code> to play with. Services like Docker and cloud providers built out their infrastructure without considering this, and in the process of patching IPv6 support in, they didn&#x27;t adhere to these best practices, leading to struggles downstream.</p></div>]]></description>
            <link>https://breq.dev/2021/09/06/inv6</link>
            <guid isPermaLink="false">/2021/09/06/inv6</guid>
            <category><![CDATA[networking]]></category>
            <pubDate>Mon, 06 Sep 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[PocKey - RP2040, SH1107, and lessons from a failed project]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a story about a project I made that failed. By &quot;failed,&quot; I don&#x27;t mean just something that didn&#x27;t perform well or didn&#x27;t meet my expectations. What I mean is, I invested countless hours into this project and I have nothing to show for it. That is, nothing but a story.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Did you come here looking for LVGL tips? Skip to <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="#sh1107">here</a>.</em></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Looking for source code? Here&#x27;s my <a href="https://github.com/breqdev/pockey" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">CircuitPython work</a> and my <a href="https://github.com/breqdev/pocketdeck" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RP2040 SDK work</a> on GitHub.</em></p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-premise">The Premise</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For years, I had been wanting to build a musical instrument using Adafruit&#x27;s <a href="https://www.adafruit.com/product/3954" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">NeoTrellis</a> kit. This board, combined with a silicone keypad, creates a surface of 16 buttons with individual RGB lighting.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="970" height="727" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpockey%2Fneotrellis.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpockey%2Fneotrellis.jpg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpockey%2Fneotrellis.jpg&amp;w=2048&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Picture from Adafruit demonstrating the NeoTrellis system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This arrangement reminded me of the <a href="https://novationmusic.com/en/launch/launchpad-x" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Novation Launchpad</a> with its arrangement of colorful buttons. I decided to try to make a similar USB MIDI controller.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I then had to decide which microcontroller to pick. At the time, the <a href="https://www.raspberrypi.org/documentation/rp2040/getting-started/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RP2040</a> from Raspberry Pi was new, and I wanted to experiment with it. I eventually went with Adafruit&#x27;s <a href="https://www.adafruit.com/product/4884" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RP2040 Feather</a>, and I threw in an <a href="https://www.adafruit.com/product/4650" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">SH1107-based display add-on</a> as an afterthought.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I called it &quot;PocKey,&quot; both because it was a pocket keypad/keyboard, and because I like Pocky.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="physical-construction">Physical Construction</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This, surprisingly, went pretty well. I mocked up a basic clamshell design in SketchUp...</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1123" height="671" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpockey%2Fcase-cad.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpockey%2Fcase-cad.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpockey%2Fcase-cad.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Everything pretty much fit on the first try, except for a few issues with the Feather mounting holes:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="996" height="1328" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpockey%2Fcase-printed.jpg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fpockey%2Fcase-printed.jpg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fpockey%2Fcase-printed.jpg&amp;w=2048&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Sorry about the horrible picture quality, I got a new phone with a better camera about a month after I took this picture...</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With that out of the way, I turned to software.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="circuitpython">CircuitPython</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I hadn&#x27;t done much with <a href="https://micropython.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">MicroPython</a> before (or with <a href="https://circuitpython.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">CircuitPython</a>, Adafruit&#x27;s fork). I had used it a bit with the ESP8266, but hadn&#x27;t had much success.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thankfully, I had a much better experience this time around, at least at first. Adafruit publishes CircuitPython libraries for pretty much all of their boards, which really made throwing things together a lot easier. With the hardware interfacing abstracted away, I just had to focus on the application logic.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="tangent-hot-reload">Tangent: Hot-Reload</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here is where the scope of the project started to expand a bit. I decided it would be interesting to have multiple loadable &quot;apps&quot; on the device, such as a macro keyboard, MIDI controller, or even some simple games. While I was building out an app loader, CircuitPython&#x27;s <a href="https://learn.adafruit.com/welcome-to-circuitpython/creating-and-editing-code#editing-code-2977443-18" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">hot reload</a> functionality started to get in the way.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To edit code, open the code.py file on your CIRCUITPY drive into your editor. Make the desired changes to your code. Save the file. That&#x27;s it! Your code changes are run as soon as the file is done saving.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While this functionality makes things easy for most projects, it started to get in my way, since changing a single app would require reloading the entire project, losing any persistent state. I decided to work on my own &quot;hot reload&quot; functionality.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This supporting code ended up turning into a full-on operating system, handling display updates, button interrupts, and a ton of other functionality. But it did end up making apps easier to write. Here&#x27;s a basic one I wrote that provides macro keys -- two keys on the main board, and an additional one on the display:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> usb_hid</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> adafruit_hid</span><span class="" style="color:#ff218c">.</span><span class="">keyboard </span><span class="" style="color:#8B5CF6">import</span><span class=""> Keyboard</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> adafruit_hid</span><span class="" style="color:#ff218c">.</span><span class="">keyboard_layout_us </span><span class="" style="color:#8B5CF6">import</span><span class=""> KeyboardLayoutUS</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> adafruit_hid</span><span class="" style="color:#ff218c">.</span><span class="">keycode </span><span class="" style="color:#8B5CF6">import</span><span class=""> Keycode</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">from</span><span class=""> pockey</span><span class="" style="color:#ff218c">.</span><span class="">app </span><span class="" style="color:#8B5CF6">import</span><span class=""> App</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">class</span><span class=""> </span><span class="" style="color:#404040">KeyboardApp</span><span class="" style="color:#ff218c">(</span><span class="">App</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">__init__</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">,</span><span class=""> pockey</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#1bb3ff">super</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="">__init__</span><span class="" style="color:#ff218c">(</span><span class="">pockey</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">keyboard </span><span class="" style="color:#8B5CF6">=</span><span class=""> Keyboard</span><span class="" style="color:#ff218c">(</span><span class="">usb_hid</span><span class="" style="color:#ff218c">.</span><span class="">devices</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">layout </span><span class="" style="color:#8B5CF6">=</span><span class=""> KeyboardLayoutUS</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">.</span><span class="">keyboard</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">mapping </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Hello World!&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;https://google.com/\n&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#1bb3ff">&#x27;A&#x27;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Hello hello!&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">setup</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">pockey</span><span class="" style="color:#ff218c">.</span><span class="">text</span><span class="" style="color:#ff218c">.</span><span class="">enabled </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">True</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">pockey</span><span class="" style="color:#ff218c">.</span><span class="">text</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Keyboard Demo&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">pockey</span><span class="" style="color:#ff218c">.</span><span class="">trellis</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">pockey</span><span class="" style="color:#ff218c">.</span><span class="">trellis</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">255</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">handle_button</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">,</span><span class=""> number</span><span class="" style="color:#ff218c">,</span><span class=""> edge</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">if</span><span class=""> edge </span><span class="" style="color:#8B5CF6">==</span><span class=""> self</span><span class="" style="color:#ff218c">.</span><span class="">pockey</span><span class="" style="color:#ff218c">.</span><span class="">PRESSED</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            </span><span class="" style="color:#8B5CF6">if</span><span class=""> number </span><span class="" style="color:#8B5CF6">in</span><span class=""> self</span><span class="" style="color:#ff218c">.</span><span class="">mapping</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">                self</span><span class="" style="color:#ff218c">.</span><span class="">layout</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">.</span><span class="">mapping</span><span class="" style="color:#ff218c">[</span><span class="">number</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">mainloop</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">pass</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">teardown</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">keyboard</span><span class="" style="color:#ff218c">.</span><span class="">release_all</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">app </span><span class="" style="color:#8B5CF6">=</span><span class=""> KeyboardApp</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So far, things were going great! I did notice that the apps were starting to feel a bit sluggish, however...</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="unreasonable-defaults-auto-writes-and-auto-refreshes">Unreasonable Defaults: Auto-Writes and Auto-Refreshes</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The CircuitPython documentation makes it clear that CircuitPython is intended primarily as an educational tool.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The easiest way to program microcontrollers: CircuitPython is a programming language designed to simplify experimenting and learning to code on low-cost microcontroller boards.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Going into this, I wasn&#x27;t expecting CircuitPython to be the highest-performance library out there by any means. That said, I was still surprised with the amount of tweaking I needed to do just to get a halfway acceptable level of performance.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">CircuitPython, by default, will immediately write any updates to NeoPixel strands or graphical displays. Admittedly, this makes things a bit easier to get started with--when I was just starting out with embedded software, I always forgot to call <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">display.show()</code> to push updates to the screen, and I would sit there and wonder why nothing was happening.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">That said, this approach makes performance much worse. The reason for this is straightforward: generally, multiple things are drawn on the screen at a time, and doing a refresh for each one of these things will be a lot slower than just doing a single refresh at the end.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Fortunately, this behavior was simple enough to disable by setting <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">trellis.pixels.auto_write = False</code> and passing <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">auto_refresh=False</code> to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">adafruit_displayio_sh1107.SH1107()</code> constructor.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="update-diffing-and-other-trickery">Update diffing and other trickery</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At this point, I was still having trouble with performance. I decided to try to reduce unnecessary updates further by calculating the difference between the new and old state. Here&#x27;s how I did that with the NeoPixels:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">sync</span><span class="" style="color:#ff218c">(</span><span class="">self</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    self</span><span class="" style="color:#ff218c">.</span><span class="">trellis</span><span class="" style="color:#ff218c">.</span><span class="">sync</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    virtual </span><span class="" style="color:#8B5CF6">=</span><span class=""> self</span><span class="" style="color:#ff218c">.</span><span class="">virtual</span></div><div class="" style="color:#404040"><span class="">    actual </span><span class="" style="color:#8B5CF6">=</span><span class=""> self</span><span class="" style="color:#ff218c">.</span><span class="">actual</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    dirty </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">False</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">for</span><span class=""> pixel </span><span class="" style="color:#8B5CF6">in</span><span class=""> </span><span class="" style="color:#1bb3ff">range</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">16</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">if</span><span class=""> virtual</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">!=</span><span class=""> actual</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            self</span><span class="" style="color:#ff218c">.</span><span class="">trellis</span><span class="" style="color:#ff218c">.</span><span class="">pixels</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> virtual</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            actual</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> virtual</span><span class="" style="color:#ff218c">[</span><span class="">pixel</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="">            dirty </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">True</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> dirty</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        self</span><span class="" style="color:#ff218c">.</span><span class="">trellis</span><span class="" style="color:#ff218c">.</span><span class="">pixels</span><span class="" style="color:#ff218c">.</span><span class="">show</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I did some similar processing of the display output by tracking the text that was on the screen. While this did significantly improve performance, it still wasn&#x27;t up to the level I wanted.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As an aside: You might notice, in this code snippet, I used the lines <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">virtual = self.virtual</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">actual = self.actual</code>. That was yet another attempt at optimization. The rationale behind this was from <a href="https://urish.medium.com/embedded-python-cranking-performance-knob-up-to-eleven-df31a5940a63#a719" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this article by Uri Shaked</a>, which explains that caching references from <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">self</code> can improve performance.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="calling-it-quits">Calling it quits</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At this point, the &quot;button-press-to-MIDI-note&quot; latency was inconsistent and higher than I would consider acceptable. I had an application that had exploded in complexity, no good way to profile it, and no clear path to better optimization other than approaches that bordered on <a href="https://en.wikipedia.org/wiki/Cargo_cult_programming" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">cargo cult programming</a>. Without a clear path forward, I decided to give up on CircuitPython.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So... what to do now? The RP2040 didn&#x27;t have well-documented Arduino support at the time (<a href="https://learn.adafruit.com/rp2040-arduino-with-the-earlephilhower-core" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">this guide</a> wasn&#x27;t published until this June). I decided to try out the <a href="https://github.com/raspberrypi/pico-sdk" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">RP2040 SDK</a> from the Pi Foundation. I&#x27;m not super familiar with C++ and its toolchains, but how hard could it be?</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-rp2040-sdk">The RP2040 SDK</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The RP2040 SDK comes with great libraries... for the RP2040&#x27;s own peripherals, that is. At the time, the chip was so new that I couldn&#x27;t find compatible libraries for the NeoTrellis or the SH1107. So, I decided to try to roll my own.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="adafruit-seesaw">Adafruit Seesaw</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The NeoTrellis is based on a protocol that Adafruit created for I/O expansion devices called Seesaw. The protocol is built on I²C, and it is based off of multiple <em>modules</em>, each with its own <em>functions</em>. For instance, the NeoPixel module (which controls the RGB lighting in each key) is module number <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x0E</code>. Setting a given pixel value is function number <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">0x04</code>. There are some precise timing requirements with the protocol, but in general, it&#x27;s just about doing an I²C write to the Seesaw&#x27;s address, and setting the payload to the module and function numbers followed by any additional arguments. To read data from the Seesaw, it just takes an immediate I²C read afterward.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wrote a Seesaw driver in C++, which meant writing to NeoPixels was as easy as this:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#404040">NeoPixel</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#ff218c;font-style:italic">set</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> number</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> r</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> g</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> b</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    number </span><span class="" style="color:#8B5CF6">*=</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#9CA3AF;font-style:italic">// each pixel is 3 bytes in the buffer</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> data</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">(</span><span class="">number </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">8</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">(</span><span class="">number </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0xff</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">,</span><span class=""> g</span><span class="" style="color:#ff218c">,</span><span class=""> r</span><span class="" style="color:#ff218c">,</span><span class=""> b</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    seesaw</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        MODULE_BASE</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        BUF</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        data</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">5</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#404040">NeoPixel</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#ff218c;font-style:italic">show</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    seesaw</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">write</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        MODULE_BASE</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        SHOW</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">nullptr</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Reading data from the keypad was similarly just a set of Seesaw commands.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With this out of the way, I was feeling pretty optimistic. Sure, I had to write my own library implementing the protocol, but at least I got something working! This is when things started to take a turn...</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sh1107">SH1107</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Adafruit links a <a href="https://cdn-shop.adafruit.com/product-files/4650/4650_C14586.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">datasheet</a> with information about the display. However, it includes almost no information other than the I²C messages to send for the OLED startup procedure. It doesn&#x27;t even list the available I²C commands. And, parts of it are written in Chinese with either no English translation or an incomprehensible attempt at one. I ended up basing my implementation mostly off of another datasheet I found <a href="https://www.displayfuture.com/Display/datasheet/controller/SH1107.pdf" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">here</a>, as well as using Adafruit&#x27;s CircuitPython SH1107 library as a reference.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The SH1107 framebuffer mapping is somewhat unconventional compared to other hobbyist displays. Most displays allocate one or more bytes per pixel, but the SH1107 only uses one <em>bit</em> per pixel, since it is a monochrome screen.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="2550" height="3300" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fpockey%2Fsh1107-mapping.png&amp;w=3840&amp;q=75 1x" src="/_next/image?url=%2Fimages%2Fpockey%2Fsh1107-mapping.png&amp;w=3840&amp;q=75"/></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="lvgl">LVGL</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now that I understood the SH1107 mapping, I needed a library that could draw simple shapes and text onto the screen. A popular option I found was LVGL. I had been wanting to try LVGL for a long time, mostly because it&#x27;s the default graphics library for <a href="https://pros.cs.purdue.edu/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PROS</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I got to work porting LVGL to the SH1107, and I immediately noticed that it was going to take some trickery to get LVGL to play nice with this weird pixel mapping. Thankfully, LVGL provides a few callbacks that can be customized:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">LVGL Display Driver Callback</th><th class="border border-black p-2 dark:border-white">Description</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">flush_cb</code></td><td class="border border-gray-500 p-2">Callback to flush the display buffer data to the display hardware</td></tr><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rounder_cb</code></td><td class="border border-gray-500 p-2">Callback to broaden the update area if necessary to ensure it lines up with display pages</td></tr><tr><td class="border border-gray-500 p-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">set_px_cb</code></td><td class="border border-gray-500 p-2">Callback to set a specific pixel value within the display buffer</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I got to work on my implementations.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rounder_cb"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">rounder_cb</code></h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This one is the most straightforward. All I needed to do was ensure that the X coordinates of the update area lined up with the page boundaries (every 8 pixels).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Within a page, all the pixels in any row are stored in the same byte. Therefore, if we want to update part of the display, we need to ensure that the area we&#x27;re updating is aligned to the page boundaries, since we can&#x27;t update individual bits.</p>
<img class="mx-auto" src="/images/diagrams/sh1107-round.svg" alt="" width="264" height="223"/>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">In this diagram, each vertical black line represents a boundary between two pages. The original update area, shown in red, is extended to line up with the page boundaries.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by rounding the first coordinate down to the nearest multiple of 8 by masking off the last three bits. Then, I did the same for the second coordinate, adding 7 to bring it to the end of the page.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#404040">Display</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#ff218c;font-style:italic">round</span><span class="" style="color:#ff218c">(</span><span class="">lv_disp_drv_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> disp_drv</span><span class="" style="color:#ff218c">,</span><span class=""> lv_area_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> area</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x1 </span><span class="" style="color:#8B5CF6">=</span><span class=""> area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x1 </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">~</span><span class="" style="color:#8B5CF6">0x7</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x2 </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x2 </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">~</span><span class="" style="color:#8B5CF6">0x7</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> </span><span class="" style="color:#8B5CF6">7</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="set_px_cb"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">set_px_cb</code></h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, I took on the callback for setting pixel values.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by calculating the page, column, and bit of the given pixel. Then came the tricky part--I found the number of bytes per page. LVGL sometimes only updates part of the display at a time, so it might decide to only update half of the display.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I found the offset into the buffer, and used a mask to flip the bit.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#404040">Display</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#ff218c;font-style:italic">set_pixel</span><span class="" style="color:#ff218c">(</span><span class="">lv_disp_drv_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> disp_drv</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> buf</span><span class="" style="color:#ff218c">,</span><span class=""> lv_coord_t buf_w</span><span class="" style="color:#ff218c">,</span><span class=""> lv_coord_t x</span><span class="" style="color:#ff218c">,</span><span class=""> lv_coord_t y</span><span class="" style="color:#ff218c">,</span><span class=""> lv_color_t color</span><span class="" style="color:#ff218c">,</span><span class=""> lv_opa_t opa</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> page </span><span class="" style="color:#8B5CF6">=</span><span class=""> x </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> column </span><span class="" style="color:#8B5CF6">=</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> bit </span><span class="" style="color:#8B5CF6">=</span><span class=""> x </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0x7</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> mask </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#8B5CF6">&lt;&lt;</span><span class=""> bit</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> bytes_per_page </span><span class="" style="color:#8B5CF6">=</span><span class=""> disp_buf</span><span class="" style="color:#ff218c">.</span><span class="">area</span><span class="" style="color:#ff218c">.</span><span class="">y2 </span><span class="" style="color:#8B5CF6">-</span><span class=""> disp_buf</span><span class="" style="color:#ff218c">.</span><span class="">area</span><span class="" style="color:#ff218c">.</span><span class="">y1 </span><span class="" style="color:#8B5CF6">+</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> buffer_index </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">page </span><span class="" style="color:#8B5CF6">*</span><span class=""> bytes_per_page</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> column</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">color</span><span class="" style="color:#ff218c">.</span><span class="">full </span><span class="" style="color:#8B5CF6">==</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        buf</span><span class="" style="color:#ff218c">[</span><span class="">buffer_index</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">|=</span><span class=""> mask</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">else</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        buf</span><span class="" style="color:#ff218c">[</span><span class="">buffer_index</span><span class="" style="color:#ff218c">]</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;=</span><span class=""> </span><span class="" style="color:#8B5CF6">~</span><span class="">mask</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="flush_cb"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">flush_cb</code></h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One more. For this one, I found the starting page, ending page, starting column, and number of bytes per page. I then iterated through each page.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For each page, I set the display page address to the current page, then set the column address to the starting column (since there are more than 16 columns, this is split into 2 commands, one for the high bits and one for the low bits). Finally, I found the index into the buffer, and sent the number of bytes per page.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">void</span><span class=""> </span><span class="" style="color:#404040">Display</span><span class="" style="color:#ff218c">::</span><span class="" style="color:#ff218c;font-style:italic">flush</span><span class="" style="color:#ff218c">(</span><span class="">lv_disp_drv_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> disp_drv</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">const</span><span class=""> lv_area_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> area</span><span class="" style="color:#ff218c">,</span><span class=""> lv_color_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> color_p</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> start_page </span><span class="" style="color:#8B5CF6">=</span><span class=""> area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x1 </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> end_page </span><span class="" style="color:#8B5CF6">=</span><span class=""> area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">x2 </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> start_col </span><span class="" style="color:#8B5CF6">=</span><span class=""> area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">y1</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> end_col </span><span class="" style="color:#8B5CF6">=</span><span class=""> area</span><span class="" style="color:#8B5CF6">-&gt;</span><span class="">y2 </span><span class="" style="color:#8B5CF6">+</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> start_col_high </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">start_col </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">4</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0x7</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> start_col_low </span><span class="" style="color:#8B5CF6">=</span><span class=""> start_col </span><span class="" style="color:#8B5CF6">&amp;</span><span class=""> </span><span class="" style="color:#8B5CF6">0xF</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> bytes_per_page </span><span class="" style="color:#8B5CF6">=</span><span class=""> end_col </span><span class="" style="color:#8B5CF6">-</span><span class=""> start_col</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> color_buffer </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">reinterpret_cast</span><span class="" style="color:#8B5CF6">&lt;</span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#8B5CF6">*</span><span class="" style="color:#8B5CF6">&gt;</span><span class="" style="color:#ff218c">(</span><span class="">color_p</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">for</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">uint8_t</span><span class=""> page_offset </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class="" style="color:#ff218c">;</span><span class=""> start_page </span><span class="" style="color:#8B5CF6">+</span><span class=""> page_offset </span><span class="" style="color:#8B5CF6">&lt;=</span><span class=""> end_page</span><span class="" style="color:#ff218c">;</span><span class=""> </span><span class="" style="color:#8B5CF6">++</span><span class="">page_offset</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c;font-style:italic">send_command</span><span class="" style="color:#ff218c">(</span><span class="">PAGE_ADDR </span><span class="" style="color:#8B5CF6">|</span><span class=""> start_page </span><span class="" style="color:#8B5CF6">+</span><span class=""> page_offset</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c;font-style:italic">send_command</span><span class="" style="color:#ff218c">(</span><span class="">COL_ADDR_LOW </span><span class="" style="color:#8B5CF6">|</span><span class=""> start_col_low</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c;font-style:italic">send_command</span><span class="" style="color:#ff218c">(</span><span class="">COL_ADDR_HIGH </span><span class="" style="color:#8B5CF6">|</span><span class=""> start_col_high</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">uint16_t</span><span class=""> buffer_index </span><span class="" style="color:#8B5CF6">=</span><span class=""> page_offset </span><span class="" style="color:#8B5CF6">*</span><span class=""> bytes_per_page</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">uint8_t</span><span class="" style="color:#8B5CF6">*</span><span class=""> data </span><span class="" style="color:#8B5CF6">=</span><span class=""> color_buffer </span><span class="" style="color:#8B5CF6">+</span><span class=""> buffer_index</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#ff218c;font-style:italic">send_data</span><span class="" style="color:#ff218c">(</span><span class="">data</span><span class="" style="color:#ff218c">,</span><span class=""> bytes_per_page</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">lv_disp_flush_ready</span><span class="" style="color:#ff218c">(</span><span class="">disp_drv</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I write these as if this was a straightforward process. In reality, getting these callbacks right took me about a week of trial and error. I spent so long troubleshooting edge cases that only occurred for specific update area sizes, and struggling to understand how the addressing of the SH1107 worked to begin with. But finally, I had a working display driver. Now, all I needed was the USB functionality to send keystrokes or MIDI input to the computer.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="tinyusb">TinyUSB</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The RP2040 SDK includes TinyUSB as a high-level USB library. The Pi Foundation provides no documentation for this library. The TinyUSB docs say that...</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is relatively simple to incorporate tinyusb into your (existing) project</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">...but they provide almost no documentation. Seriously, what is &quot;Implement all enabled classes&#x27;s [sic] callbacks&quot;??? What classes are enabled? What classes should I enable? What callbacks do they have? What does my implementation need to include???</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It is at this point where I gave up on this project.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Well, this is it. I&#x27;m faced with a project I spent countless hours on, without anything to show for it. So what did I learn?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Introducing abstractions for short-term speedup can lead to technical debt in the long run.</strong> By trying to optimize the CircuitPython build as much as possible, I introduced complexity that left me with an application that was harder to understand. Without clear knowledge as to what my code was doing, I was left without any clear way to improve it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>The flashiest solution isn&#x27;t always the best.</strong> If I had picked a chip with stable Arduino support, I could&#x27;ve taken advantage of existing libraries while keeping the speed of C++. Choosing the brand new RP2040 put me on the bleeding edge.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Pick the right tool for the job.</strong> Python, on a microcontroller, for a latency-critical application... Even though CircuitPython let me get up and running quickly, it couldn&#x27;t achieve what I was targeting, forcing me to rewrite everything from scratch in C++.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Fail early.</strong> One of the main dealbreakers of the project was that the keypad buttons didn&#x27;t respond well to &quot;drumming&quot; input--they needed to be completely pressed down. Instead of recognizing this flaw in the design, I kept investing effort into the project anyway.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Focus on the MVP.</strong> In the early stages, I invested a lot of time in building out the app-specific hot reload feature. If I had only focused on the core MIDI functionality, I would have faced the latency and usability issues a lot sooner. By worrying about side features instead of the minimum viable product, I was delaying the inevitable.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="epilogue">Epilogue</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ironically, two weeks after I gave up on this project, Adafruit released an extremely similar design <a href="https://www.adafruit.com/product/5128" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">as a kit</a>, trading the three buttons for an encoder and the silicone keypad for Cherry MX (clone) switches.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;m not disappointed in myself for how this project went. I learned more about bit operations in embedded programming, the mechanics of I²C, and the tooling required to manage a large C++ project. I also learned some important lessons about project management and planning. I&#x27;m glad I&#x27;m experiencing failure like this now, when the only casualty is a bit of my spare time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Oh, and when I said I had nothing to show... that isn&#x27;t <em>entirely</em> true.</p>
<div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/4HvcbrAWKGY/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/4HvcbrAWKGY/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div></div>]]></description>
            <link>https://breq.dev/2021/08/29/pockey</link>
            <guid isPermaLink="false">/2021/08/29/pockey</guid>
            <category><![CDATA[c++]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Sun, 29 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[React Twitter NoTrack]]></title>
            <description><![CDATA[<div class="e-content font-body"><div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">react-twitter-notrack</code> does exactly what it says on the tin: allow you to embed Tweets as React components without exposing your users to tracking. <del>You can install it from <a href="https://www.npmjs.com/package/react-twitter-notrack" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">npm</a> with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">npm i react-twitter-notrack</code>.</del></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><em>Unfortunately, things seem to have stopped working, probably as a result of Twitter improving their bot detection or shutting down their API. There were enough tradeoffs inherent in this project that I don&#x27;t intend to continue maintaining it. Thus, it is no longer usable.</em></p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="">Tweet</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class=""> </span><span class="" style="color:#8B5CF6">from</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;react-twitter-notrack&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">App</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">        </span><span class="" style="color:#8B5CF6">&lt;</span><span class="">Tweet</span><span class=""> id</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#1bb3ff">&quot;20&quot;</span><span class=""> apiUrl</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#1bb3ff">&quot;https://twitter-proxy.breq.workers.dev&quot;</span><span class="" style="color:#8B5CF6">&gt;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to be able to embed Tweets on my website, so I looked into React-based Twitter embed libraries. The two that I found, <a href="https://github.com/andrewsuzuki/react-twitter-widgets" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">react-twitter-widgets</code></a> and <a href="https://github.com/saurabhnemade/react-twitter-embed" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">react-twitter-embed</code></a> operated similarly: they both used the Twitter widgets.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Twitter has a <a href="https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/set-up-twitter-for-websites" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">system of widgets</a> that can be embedded on a website. They work by using a JavaScript library to dynamically include iFrames onto the page. I&#x27;m not a huge fan of this approach, for a couple reasons:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">The iFrames tend to load in after everything else on the page, leading to a huge layout jump.</li>
<li class="my-2 pl-2">Embedding iFrames uses more resources.</li>
<li class="my-2 pl-2">Users who use tracker-blockers, like Firefox Enhanced Tracking Protection, might not see the Tweet at all.</li>
<li class="my-2 pl-2">Allowing Twitter to execute JavaScript on my webpage exposes my users to tracking without their consent.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to try making my own Twitter embed, built as a pure React component without any imperative DOM manipulation.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started building out this project using <a href="https://storybook.js.org/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Storybook</a>. I had wanted to try out Storybook for a while by now. I really enjoyed how quickly it allowed me to iterate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using an official Twitter widget as a model, I tried to emulate the design as best I could. I built out this mockup using <a href="https://styled-components.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">styled-components</code></a>, which was another first for me. I think that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">styled-components</code> was a good fit for this project--it&#x27;s a lot lighter-weight than Tailwind, and there was no need for me to stick to a broader design system like my website as a whole. That said, for more ambitious projects, I&#x27;ll probably stick with Tailwind to keep things looking cohesive.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then came the data fetching. I noticed that the official Twitter embed was sending a request to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">cdn.syndication.twimg.com</code>:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="601" height="490" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ftwitter%2Fnetwork-request.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Ftwitter%2Fnetwork-request.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ftwitter%2Fnetwork-request.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">...and getting back a response with info about the tweet:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="599" height="619" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ftwitter%2Fresponse.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Ftwitter%2Fresponse.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ftwitter%2Fresponse.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, just call this endpoint from the React component and we&#x27;re good, right?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">...nope. Twitter uses <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">CORS</a> to only allow <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">platform.twitter.com</code> to access this endpoint. For the official embed, this isn&#x27;t an issue, since the iFrame is loaded from that origin. But for our pirate embed, we&#x27;ll need to find another way.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I ended up building a proxy with Cloudflare Workers to spoof the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Origin</code> header to Twitter and send back a permissive <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Access-Control-Allow-Origin</code> to the client. This is pretty much the same approach I used for GenReGen&#x27;s <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/genregen#technical-description">Pastebin proxy</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With that out of the way, I just had to fetch the data from the proxy. I used trusty old <a href="https://swr.vercel.app/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">useSWR</a> to get the job done.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This library doesn&#x27;t produce embeds with as much polish as the official Twitter ones -- they don&#x27;t show the original tweet when embedding a reply or quote tweet, and they don&#x27;t show more than one image at a time for now. But overall, I think this resulted in something usable and more performant than the official embed. For a while, I used it for all the Twitter embeds on this website, and it checked all the boxes. I don&#x27;t know if anyone else ever found it useful, but I&#x27;m still happy that I shared my work on NPM.</p></div>]]></description>
            <link>https://breq.dev/projects/react-twitter-notrack</link>
            <guid isPermaLink="false">/projects/react-twitter-notrack</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[react]]></category>
            <pubDate>Fri, 27 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Building My Online Presence]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve always wanted to keep a personal website up and running. In part, that&#x27;s because I want to make sure everything I work on has a home; hopefully, people can use my project writeups for inspiration or advice on their own creative endeavours. It&#x27;s also partly because I want a place to share my thoughts. The main reason, however, is to keep a centralized directory of who I am.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For much of my life, my identity has been, for lack of a better word, unstable. This is an outlet for me to keep track of what makes me who I am. It&#x27;s a place where I can post things that I&#x27;m proud of.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">(Funnily enough, it was this period of questioning that led me to pick the name <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">breq.dev</code>. At the time, I knew I was deeply uncomfortable with my name, but I didn&#x27;t yet understand why. When it came time to pick a domain name, I ended up scrolling through the <a href="https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">AVR instruction set</a> until I found something somewhat pronounceable, and it&#x27;s kinda stuck!)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With that out of the way, here&#x27;s the journey my online presence took from both a technical and personal perspective.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="my-first-html">My First HTML</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first HTML file I ever hosted publicly was published on August 19, 2015. Here&#x27;s that fateful commit:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1046" height="541" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Ffirst-commit.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Ffirst-commit.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Ffirst-commit.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As a 12-year-old with no money, it&#x27;s no surprise I turned to <a href="https://pages.github.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">GitHub Pages</a> for hosting. And, as you can tell from the commit, I hadn&#x27;t exactly figured out code indentation yet.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Over the years, this site turned into a disorganized collection of random <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.html</code> files I happened to experiment with. This included <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/gemini">Gemini</a> and <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/bounce">Bounce</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-paper-feed">The Paper Feed</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In around 2019, I started to take this website idea a bit more seriously. I started what I called the Paper Feed -- a single-page site where I would post some thoughts every couple weeks. Here&#x27;s the only surviving screenshot (which is probably for the best):</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="793" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fpaper-feed.png&amp;w=828&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fpaper-feed.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fpaper-feed.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It wasn&#x27;t much -- just a basic CSS theme. But it was mine, and it was the first time I started to see my online presence as something I should curate and something that people might be interested in.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="jekyll-and-the-beginnings-of-the-project-log">Jekyll, and the beginnings of the project log</h2>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="830" height="486" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-initial.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-initial.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-initial.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ouch. The lack of contrast hurts my eyes.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You can spot a few cool things in this revision though:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">No more deadnames to remove! Yay!</li>
<li class="my-2 pl-2">My logo -- the cube with the half adder -- makes its first appearance.</li>
<li class="my-2 pl-2">&quot;Welcome to my little patch of internet&quot; is still my website slogan to this day.</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It didn&#x27;t take long for me to start throwing more interesting stuff on that site, however:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1046" height="827" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-real-initial.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-real-initial.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fjekyll-real-initial.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started to really use CSS for things at this point, as you can tell. This page was built with Bootstrap as a base. I also started to make use of Jekyll&#x27;s layout features to reduce duplication across pages, and the data features to define the navbar with YAML.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The site had two pages: an &quot;about&quot; page listing my contact info, and a &quot;projects&quot; page containing writeups for all of the projects. Usability wasn&#x27;t great -- the project page was a giant wall of text -- but those initial descriptions grew into the writeups I currently have on my site.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1115" height="1393" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fparallax-bg.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fparallax-bg.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fparallax-bg.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next came the wireframe logo. Projects started to go into their own pages as well. Things were really starting to take shape.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1115" height="1393" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fautoplay-videos.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fautoplay-videos.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fautoplay-videos.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I added autoplaying videos to bring my projects front and center.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1115" height="900" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fpink-and-blue.png&amp;w=1200&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fpink-and-blue.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fpink-and-blue.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">As I started to accumulate more projects, I switched to a tile-based layout to fit more on the homepage.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="spinning-off-jekyll-theme-breq">Spinning off <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">jekyll-theme-breq</code></h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After this, I decided to spin off a Jekyll theme with my navbar style into a separate package. I&#x27;m still using this theme for my <a href="https://emoji.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">emoji keyboard</a>.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1031" height="830" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Frot-logo.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Frot-logo.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Frot-logo.png&amp;w=3840&amp;q=75"/></div>
 
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1031" height="830" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwebsite%2Fnew-tiles.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwebsite%2Fnew-tiles.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwebsite%2Fnew-tiles.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Not much has changed really here -- I&#x27;ve switched to a light theme which is easier on the eyes, and I&#x27;ve added a big autoplaying video to the homepage. And, finally, I put the name &quot;Brooke&quot; in big letters! I gotta be honest, that felt good.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="gatsby-and-false-starts">Gatsby, and false starts</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At this point, however, I was starting to feel like I&#x27;d reached the limits of what Jekyll could comfortably handle. Project information was split up between each project file and the YAML data files, and splitting off my theme into its own project hadn&#x27;t made things simpler like I&#x27;d hoped.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to make the switch to Gatsby. It was based on React, which I had heard about but wasn&#x27;t too comfortable with initially. I was, however, impressed by its claims of legendary performance.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It took me a couple tries to make the switch. Gatsby&#x27;s data layer took me forever to get used to. Thinking of each resource as a &quot;node&quot; and understanding how each step in the pipeline would process that node was a big shift in how I thought about content and data.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On my third or so attempt to learn Gatsby, I ended up with something feature-complete and decided to make the switch. And I&#x27;m glad I did -- I&#x27;m starting to really love React, and the image optimization, data prefetching, and simple hot reload that Gatsby brings to the table have made my final site much easier to develop and interact with.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also switched to Tailwind for CSS around this time, and found it to be the perfect styling system for working with React components. Having CSS in a separate file, even with CSS modules, was something I always tried to find a way around. Tailwind, on the other hand, helps me stay organized.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="where-we-are-now">Where we are now</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I think I&#x27;ve settled on a model that works, both in terms of my technology stack and in terms of the role this website serves in my life.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Preserving my work is important to me -- even if nobody else ever reads it, I want to have this here as a resource for myself, about myself, to remind me of who I am.</p></div>]]></description>
            <link>https://breq.dev/2021/08/26/website</link>
            <guid isPermaLink="false">/2021/08/26/website</guid>
            <category><![CDATA[web]]></category>
            <category><![CDATA[javascript]]></category>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[GenReGen]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1031" height="830" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fgenregen.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fgenregen.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fgenregen.png&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">&quot;Genre Gen&quot;-erator? &quot;Gen&quot;-erate &quot;Regen&quot;-erate? Regardless of what the name means, this is a program that&#x27;ll take in a list of items and spit out randomized mashups.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I received an email from <a href="https://deadline.com/2020/07/gerard-butler-screenwriter-mitchell-lafortune-signs-with-apa-1203000985/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">my uncle Mitch</a> asking for a random genre mashup generator to help with coming up with ideas for movies. I wanted to make something stable, that would continue working even without my involvement.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The brief was simple enough -- just pull two random items from a list. I didn&#x27;t even use a framework for this; it&#x27;s all done with imperative DOM manipulation. Since it&#x27;s just a few static files, I&#x27;m hosting it on Cloudflare Pages. Implementing a few other features, like going backwards through the list of generated mashups, was also pretty trivial.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What complicated things a bit was Mitch&#x27;s request that the list be editable. I didn&#x27;t want to manage an authentication system to restrict access to a central list, since that would require hosting and a custom backend solution (which is more expensive to run, more difficult to implement, and more work to maintain).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The hack that I settled on was using Pastebin as a source. Anyone can upload a list of things to Pastebin, then put the URL of that list in the &quot;source list&quot; box to generate random pairings from that list. The JavaScript will detect changes to this field and pull the list from Pastebin, using it for all subsequent mashups. It&#x27;ll even save the most recent URL in the browser <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">localStorage</code>, so that the user doesn&#x27;t have to keep re-pasting it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Of course, there was another hurdle: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">CORS</a>. Yes, it helps keep us safe on the web, yes, it protects private data and intellectual property rights... but in this case, it was just another issue to work around.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Oh, but Pastebin adds the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Access-Control-Allow-Origin</code> header, right...?</p>
<div></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Ah, capitalism strikes again.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To solve this, I ended up using <a href="https://workers.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Cloudflare Workers</a> to make a simple proxy that adds CORS headers on top of the Pastebin API.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c;font-style:italic">addEventListener</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;fetch&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="">event</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  event</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">respondWith</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c;font-style:italic">handleRequest</span><span class="" style="color:#ff218c">(</span><span class="">event</span><span class="" style="color:#ff218c">.</span><span class="">request</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#8B5CF6">catch</span><span class="" style="color:#ff218c">(</span><span class=""></span></div><div class="" style="color:#404040"><span class="">      </span><span class="" style="color:#ff218c">(</span><span class="">err</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">=&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Response</span><span class="" style="color:#ff218c">(</span><span class="">err</span><span class="" style="color:#ff218c">.</span><span class="">stack</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">status</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">500</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">async</span><span class=""> </span><span class="" style="color:#8B5CF6">function</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">handleRequest</span><span class="" style="color:#ff218c">(</span><span class="">request</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> incomingOrigin </span><span class="" style="color:#8B5CF6">=</span><span class=""> request</span><span class="" style="color:#ff218c">.</span><span class="">headers</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">get</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Origin&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">if</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">!</span><span class="">/</span><span class="">(breq\.dev|genregen\.pages\.dev)</span><span class="">/</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">test</span><span class="" style="color:#ff218c">(</span><span class="">incomingOrigin</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#8B5CF6">return</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Response</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Origin Not Allowed&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""> </span><span class="" style="color:#8B5CF6">status</span><span class="" style="color:#8B5CF6">:</span><span class=""> </span><span class="" style="color:#8B5CF6">403</span><span class=""> </span><span class="" style="color:#ff218c">}</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> url </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">URL</span><span class="" style="color:#ff218c">(</span><span class="">request</span><span class="" style="color:#ff218c">.</span><span class="">url</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">const</span><span class=""> paste </span><span class="" style="color:#8B5CF6">=</span><span class=""> url</span><span class="" style="color:#ff218c">.</span><span class="">searchParams</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">get</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;paste&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  request </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Request</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;https://pastebin.com/raw/&quot;</span><span class=""> </span><span class="" style="color:#8B5CF6">+</span><span class=""> paste</span><span class="" style="color:#ff218c">,</span><span class=""> request</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  request</span><span class="" style="color:#ff218c">.</span><span class="">headers</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">set</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Origin&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;pastebin.com&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">let</span><span class=""> response </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">await</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">fetch</span><span class="" style="color:#ff218c">(</span><span class="">request</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  response </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">new</span><span class=""> </span><span class="" style="color:#404040">Response</span><span class="" style="color:#ff218c">(</span><span class="">response</span><span class="" style="color:#ff218c">.</span><span class="">body</span><span class="" style="color:#ff218c">,</span><span class=""> response</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  response</span><span class="" style="color:#ff218c">.</span><span class="">headers</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">set</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Access-Control-Allow-Origin&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> incomingOrigin</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">  response</span><span class="" style="color:#ff218c">.</span><span class="">headers</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c;font-style:italic">set</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;Vary&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;Origin&quot;</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">  </span><span class="" style="color:#8B5CF6">return</span><span class=""> response</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Honestly, it does everything I had hoped it would. The Pastebin solution is a bit janky, but in the end, I&#x27;m glad I got everything working without any recurring costs or complex backends to maintain.</p></div>]]></description>
            <link>https://breq.dev/projects/genregen</link>
            <guid isPermaLink="false">/projects/genregen</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[serverless]]></category>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Wall Matrix]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/o5zavmZU38s/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/o5zavmZU38s/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A video of me doing the final assembly of the display.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a sign that I built that hangs on the wall and shows information from the Internet.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I bought an <a href="https://www.adafruit.com/product/420" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">LED matrix panel</a> from Adafruit years ago, and I&#x27;ve tried to use it to display things in the past, but I had never figured out a good way to mount it. Around the time I built this project, I had just gotten some threaded inserts for use with 3D printing, and I figured making a case for this display would be a good project to use them for.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hardware">Hardware</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My 3D printer can only do up to 120mm in each dimension, so I needed to split the print up into two parts. I used threaded inserts to join these together, and I did the split slightly off-center so that the mounting hole would be stronger.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To attach the Raspberry Pi, I had hoped to screw it into the threaded inserts as well, but the RPi&#x27;s mounting holes are M2.5 while the inserts I got are M3. I ended up making pegs that fit into the Pi&#x27;s mounting holes, and a &quot;seat belt&quot; to hold the Pi against them. This solution was surprisingly sturdy.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, it came time to connect the display to the Pi. While Adafruit sells a <a href="https://www.adafruit.com/product/2345" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">HAT</a> to make the connections easy, it <a href="https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/wiring.md#alternative-hardware-mappings" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">doesn&#x27;t use the default mapping</a> for the library I had planned on using. I decided to build my own on a <a href="https://www.adafruit.com/product/2310" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Perma-Proto</a>, and to leave room in case I wanted to add other matrix strands or devices in the future.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="software">Software</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The matrix is driven using <a href="https://github.com/hzeller/rpi-rgb-led-matrix" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">hzeller&#x27;s rpi-rgb-led-matrix</a> library. Specifically, I&#x27;m making use of the Python bindings.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The code is split into four major sections:</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-driver">The Driver</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The driver exposes an interface allowing other components in the stack to send an image to the matrix. I wrote two drivers: a &quot;real&quot; driver that shows the image on the matrix, and a &quot;fake&quot; driver that draws the image in a TkInter window. This allows me to test out new sources, images, and designs on my own computer before deploying them to the Pi.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-server">The Server</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The server is written in Flask, but I&#x27;ve turned off the threading abilities so that the driver can remain a singleton. (Normally, this is a bad idea, but I&#x27;ve decided that it&#x27;s fine, since this is an embedded device anyway.) It loads in any available sources, retrieves an image from the current source, and handles incoming requests to change the active source or interrupt the source with a scrolling message.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The server and driver communicate over a message queue. The server will send a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">SOURCE_CHANGED</code> message when the user chooses a different source, and it will send a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">FLASH_MESSAGE</code> message when the user submits a message to show.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-sources">The Sources</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve written four basic sources so far.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>ColorBars</strong>: Shows the basic color bar test image on the screen.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="699" height="700" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fweather.jpg&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Weather</strong>: Shows the current time, temperature, and weather on the screen, using the OpenWeatherMap API.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="726" height="726" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fcrypto.jpg&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fcrypto.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fcrypto.jpg&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Crypto</strong>: Shows the current price and 24-hour percent change of a cryptocurrency (defaults to ETH), using the CoinMarketCap API.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="713" height="713" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fmbta.jpg&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>MBTA</strong>: Shows the next northbound Green Line and Orange Line trains passing through the Northeastern University campus, using the MBTA&#x27;s official API.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">These sources inherit from a base <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">Source</code> class, which has some data caching logic built in. By default, APIs are only called once every 60 seconds, but the Crypto source caches data for 5 minutes due to the more restrictive API license.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-client">The Client</h4>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="750" height="799" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fwallmatrix%2Fclient.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Fwallmatrix%2Fclient.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fwallmatrix%2Fclient.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The client is written in static HTML/CSS/JS. It features a dropdown menu to change the source and a textbox to input messages to flash. I designed it to be usable on a phone, since I figured that&#x27;s how most people would like to control the sign.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The finished result looks a lot more polished than I was expecting! I had a lot of bugs to work out at first -- it turns out there are a lot more edge cases than I thought there would be when it comes to displaying time -- but it&#x27;s been quite reliable.</p></div>]]></description>
            <link>https://breq.dev/projects/wallmatrix</link>
            <guid isPermaLink="false">/projects/wallmatrix</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Bounce Homepage]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="text-center my-4 text-2xl">View the <a href="https://breq.dev/apps/bounce.html">live demo</a>!</div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Bounce was a homepage I made to make my school laptop just a bit more unique from everyone else&#x27;s. It features three bouncing circles. Pressing <kbd>space</kbd> adds circles to the screen, while pressing <kbd>c</kbd> takes them away.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to have fun and make something unique, and in the end, I was really proud of the result! It was cool to take an open-ended idea like &quot;make a more interesting web browser home page&quot; and brainstorm different implementations.</p></div>]]></description>
            <link>https://breq.dev/projects/bounce</link>
            <guid isPermaLink="false">/projects/bounce</guid>
            <category><![CDATA[canvas]]></category>
            <category><![CDATA[javascript]]></category>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Gemini]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="text-center my-4 text-2xl">View the <a href="https://breq.dev/apps/gemini.html">live demo</a>!</div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Gemini was a simple canvas game I built. The player had to guide the black circle towards the white circle using the arrow keys, but the black circle is pushed around by a randomly changing current.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to make a simple game in order to learn JavaScript. I worked on this during a summer camp at MSSM, in an introductory programming course.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Gemini was certainly a product of 2016. It was built with jQuery. The two balls are represented as JavaScript objects, but they&#x27;re created using the prototype interface, as ES2015 classes weren&#x27;t widely supported or documented at the time.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It was something I could show my friends, and it was something that helped me learn JavaScript, so I&#x27;d call it a success! That said, I&#x27;m glad my code quality has improved since I wrote this... :)</p></div>]]></description>
            <link>https://breq.dev/projects/gemini</link>
            <guid isPermaLink="false">/projects/gemini</guid>
            <category><![CDATA[canvas]]></category>
            <category><![CDATA[javascript]]></category>
            <pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Motion Sickness Fish]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/iX1t1ADwkrk/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/iX1t1ADwkrk/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Exactly what it says on the tin. It&#x27;s a Billy Bass fish that&#x27;s had its electronics replaced and had Motion Sickness loaded on.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For those of you lucky enough to not know what the Billy Bass fish is, let me enlighten you:</p>
<div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/AJF2cCMXPKk/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/AJF2cCMXPKk/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And here&#x27;s the song I loaded on:</p>
<div style="display:flex;justify-content:center"><iframe src="https://open.spotify.com/embed/track/6LxcPUqx6noURdA5qc4BAT" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My uncle and aunt asked me to take this one on. The entire project needed to be finished in about a week. I had finals for the first half of the week, and I knew it would take a while to ship to my relatives in California, but I concluded that I could barely make it work if I ordered the parts right away, rebuilt the fish as quickly as possible once they arrived, and shipped the result as soon as possible.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="hardware-and-construction">Hardware and Construction</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The chip that controls Billy Bass and plays the default music is covered in epoxy. Much smarter people than me <a href="https://www.youtube.com/watch?v=_E0PWQvW-14" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">struggle</a> to do this kind of thing. I quickly ruled this option out.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What about using the existing speakers though? Well, at the time, I didn&#x27;t have a physical Billy Bass handy (remember, shipping...) so I couldn&#x27;t be sure of the specifications of these speakers.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The easiest solution would be to completely replace the built-in electronics with my own.Knowing that I had only a few days to get this working, and no opportunity to re-order parts if something went wrong, I knew that I needed to get this right.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Normally, I would DIY as much as possible, with hand-soldered perfboards, hectic wiring, and all sorts of bodged-together boards. It became obvious to me that this was the wrong approach here. This was my first lesson: know when too much DIY just won&#x27;t cut it.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I needed a cohesive ecosystem. <a href="https://adafruit.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Adafruit</a> has been my go-to source for parts over the past couple years, and being based in NYC, I knew shipping wouldn&#x27;t take too long. But that still left me with plenty of options.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by identifying what physical hardware I&#x27;d need. Adafruit has a line of <a href="https://www.adafruit.com/?q=VS1053&amp;sort=BestMatch" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">boards</a> based off of the VS1053 chip--that takes care of my audio playing. And there are plenty of motor boards available in any form factor I would want. So what do I choose?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Adafruit&#x27;s board selection generally varies by size and power supply. The VS1053 modules were available in 3 different form factors: a simple breakout board, a FeatherWing board, and an Arduino Shield board.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The breakout board would require wiring it up to a board myself, which eliminated it from the running. Feather boards and Arduino boards are available with any sort of chip, so that&#x27;s not a concern. What differentiates the boards is physical size and power supply. Feathers are small, and can be powered off of a 5 volt source or a LiPo battery. Arduinos are much larger, but they can take anything from 6 to 20 volts.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here I faced a dilemma. I wanted the electronics to be small, so that they could fit inside the Billy Bass enclosure without any sort of external box. I also was wary of running the chip and the motors off the same power rail, due to reset issues I&#x27;d experienced in the past. The Arduino, with it&#x27;s built-in power regulator, seemed like functionally the better choice, as it could take a standard barrel plug power supply, run the motors directly off of it, and run the microcontroller chip off a clean, regulated 5V.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, I wasn&#x27;t sure if the larger Arduino form factor would fit. I considered rigging something up with a custom perfboard to attach a voltage regulator to the Feather... but I eventually decided against it. I needed to play by the rules of the ecosystem so that I could take advantage of its ease-of-use. I bought the VS1053 and the motor driver for the Arduino.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1720" height="1290" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Ffish.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Ffish.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Ffish.jpg&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Here&#x27;s the finished product! You can see the Adafruit Metro (Arduino clone),
the speakers, and not much else. Because the VS1053 chip and motor driver were
mounted directly on top of the Metro, there was no need for messy mounting
solutions or manual wiring.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="movement-sync-software">Movement Sync Software</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And now, the race was on to get something functional as fast as possible. Getting the song to play over the audio shield and speakers was easy, thanks to the well-documented Adafruit libraries. That left only the fish motion to take care of.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Getting something working this quickly would have been impossible if I had tried to roll my own amplifier circuit, or wire together my own power supply circuit, or do any of that. Here was the ecosystem at work for me, allowing me to go from idea to concept in record time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The next step was to actually move the fish. I quickly wrote functions to control the fish&#x27;s head, tail, and mouth, again using the excellent library Adafruit provided for their motor shield. Normally, I would&#x27;ve hacked something together with an L293D in order to save a buck or two. But in this case, the motor shield was worth every penny.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, the question is, how do I decide when to activate these movements?</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="onboard-audio-processing">Onboard Audio Processing?</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The official Amazon Alexa enabled Billy Bass reacts to the audio to decide when to move. This... doesn&#x27;t exactly work well, as you can see in this Linus Tech Tips review at around the 5:03 mark:</p>
<div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/4EzC9J9xOHQ/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/4EzC9J9xOHQ/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">&quot;Okay, so he just kind of spasms then. So basically, you just have like a fish out of water on your wall whenever music is playing.&quot;</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Yeah... that wasn&#x27;t gonna cut it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided I needed to manually choreograph the movements to match the music. No reacting-to-audio trickery would save me from this. I did attempt a few basic tests of reacting to audio loudness, but my results were even worse than the fish in that video.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pre-recording-movements">Pre-Recording Movements</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I realized that I would need to write some custom software if I wanted to precisely record all of these movements in relation to the song. I needed a framework that would let me play audio, handle raw keyboard events, and write in a language I could iterate quickly in. PyGame fit the bill, and I was quickly off to the races.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My script waited for the user to press the space key. It then started playing the music and keeping an internal timer. Then, whenever another key was pressed, it would log the event and the time at which it happened. Finally, it saved it to a JSON file. Pretty basic stuff.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Did I have to decouple the recording from the code generation? Probably not. But I figured it would be best to have a full record of all the movements over the course of the song, so I could try different methods of storing that on the fish if I needed to.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="storing-the-routine">Storing the routine</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If I had the luxury of time, I might&#x27;ve gone with a packed binary representation like I did for my <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/stmusic">STMusic</a> project. However, writing the code to generate, store, and load this packed representation would be a lot of work.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I eventually landed on the idea of using a Python script to take the JSON object and <em>generate C code</em> that called each movement function.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> json</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">with</span><span class=""> </span><span class="" style="color:#1bb3ff">open</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;recorded.json&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    actions </span><span class="" style="color:#8B5CF6">=</span><span class=""> json</span><span class="" style="color:#ff218c">.</span><span class="">load</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">code </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">[</span><span class="" style="color:#ff218c">]</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">action_lines </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;head&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;  fish.head();&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;tail&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;  fish.tail();&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;rest&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;  fish.rest();&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;mouthOpen&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;  fish.setMouth(1);&quot;</span><span class="" style="color:#ff218c">,</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">&quot;mouthClosed&quot;</span><span class="" style="color:#ff218c">:</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;  fish.setMouth(0);&quot;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">total_ts </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">for</span><span class=""> timestamp</span><span class="" style="color:#ff218c">,</span><span class=""> action </span><span class="" style="color:#8B5CF6">in</span><span class=""> actions</span><span class="" style="color:#ff218c">.</span><span class="">items</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    total_ts </span><span class="" style="color:#8B5CF6">+=</span><span class=""> </span><span class="" style="color:#1bb3ff">float</span><span class="" style="color:#ff218c">(</span><span class="">timestamp</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    millis </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">int</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">float</span><span class="" style="color:#ff218c">(</span><span class="">total_ts</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#8B5CF6">*</span><span class="" style="color:#8B5CF6">1000</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    code</span><span class="" style="color:#ff218c">.</span><span class="">append</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">f&quot;  delayUntil(</span><span class="" style="color:#ff218c">{</span><span class="">millis</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#1bb3ff">);&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    code</span><span class="" style="color:#ff218c">.</span><span class="">append</span><span class="" style="color:#ff218c">(</span><span class="">action_lines</span><span class="" style="color:#ff218c">[</span><span class="">action</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">generated </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;\n&quot;</span><span class="" style="color:#ff218c">.</span><span class="">join</span><span class="" style="color:#ff218c">(</span><span class="">code</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">with</span><span class=""> </span><span class="" style="color:#1bb3ff">open</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;routine.ino&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;w&quot;</span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">as</span><span class=""> </span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;void routine() {\n&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="">generated</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    </span><span class="" style="color:#1bb3ff">file</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;\n}\n&quot;</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Give me a second, I have to wash the blood off my hands after murdering every best practice in the book with this move.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To be clear--this method is <em>ugly</em>. It produces an excessively large binary, it stores data in a file that people expect to be for application logic, it adds an unintuitive step to the build process, it&#x27;s just generally Not The Way Things Are Supposed To Be Done.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">But it worked. It kept the movements synched up to the song. At the end of the day, the future maintainability or extensibility of this program isn&#x27;t something that mattered that much, and it was a worthwhile tradeoff in order to save some valuable time.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was the second major lesson. Code quality is good, and good habits exist for a reason, but it&#x27;s important to decide when things are &quot;good enough&quot; for that specific project.</p>
</blockquote>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="one-last-feature-the-volume-knob">One Last Feature: the volume knob</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After putting everything together, I noticed that the fish was perhaps too loud to have in a room. At the last minute, I decided to try adding a volume knob. But how to do it?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The &quot;knob&quot; part was easy enough: I just grabbed some spare parts from my dad&#x27;s work on repairing guitar amps. On the other hand, the &quot;volume&quot; part presented more of a challenge. Should I go for an analog approach, and try to create a circuit involving the knob that limited the output to the speaker? Or...</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had another idea. What if I used an analog input pin on the Arduino to read the value of the knob, then sent that value to the VS1053 to adjust it&#x27;s output volume in software? This greatly simplified the wiring, so I went ahead and built it.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here, I made another tradeoff. I sampled this knob after each movement--not at a regular interval, not at a reasonable sample rate, literally just after each fish movement. And it was good enough! The variable amplitude of the song made the jagged nature of this sampling less obvious.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Compromises were definitely made. The lack of built-in batteries made the project significantly more cumbersome, for one. And the motions weren&#x27;t completely perfect. That said, I&#x27;m pretty proud of making what I did in such a limited turnaround time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project taught me when good enough was good enough. I sometimes struggle with perfectionism in my projects, where I&#x27;ll rewrite the same code over and over again in pursuit of a better way of doing things. This forced me to make the tradeoffs I was too scared to make, and to decide what mattered for the final product and what didn&#x27;t.</p></div>]]></description>
            <link>https://breq.dev/projects/fish</link>
            <guid isPermaLink="false">/projects/fish</guid>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[arduino]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Tue, 08 Jun 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[5F3759DF: An explanation of the world's most infamous magic number]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a famous function in the Quake III source code. Can you guess what it does?</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">long</span><span class=""> i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">float</span><span class=""> x2</span><span class="" style="color:#ff218c">,</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">1.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	x2 </span><span class="" style="color:#8B5CF6">=</span><span class=""> number </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#8B5CF6">0.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> number</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">long</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">y</span><span class="" style="color:#ff218c">;</span><span class="">                       </span><span class="" style="color:#9CA3AF;font-style:italic">// evil floating point bit level hacking</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0x5f3759df</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> i </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">               </span><span class="" style="color:#9CA3AF;font-style:italic">// what the fuck?</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> x2 </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">   </span><span class="" style="color:#9CA3AF;font-style:italic">// 1st iteration</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">return</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This function calculates the inverse of the square root of a number.</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>Q</mi><mi mathvariant="normal">_</mi><mi>r</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mo stretchy="false">(</mo><mi>n</mi><mi>u</mi><mi>m</mi><mi>b</mi><mi>e</mi><mi>r</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mn>1</mn><msqrt><mrow><mi>n</mi><mi>u</mi><mi>m</mi><mi>b</mi><mi>e</mi><mi>r</mi></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">Q\_rsqrt(number) = \frac{1}{\sqrt{number}}</annotation></semantics></math></span></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="preliminary-explanation">Preliminary Explanation</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="why">Why?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Quake III needs to perform a lot of calculations to simulate lighting. Each face of a polygon in the scene is represented by a 3D vector pointing perpendicular to it. Then, these vectors are used to calculate the reflections of light off of the face.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Each face could be represented by a vector of any length--the important information is the direction of the vector, not the length of the vector. However, the calculations become much more simple if all of the vectors have length 1. Thus, we need to <em>normalize</em> these vectors. We can do this by dividing the vector by its length (it&#x27;s <em>norm</em>, <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">∥</mi><mi mathvariant="bold-italic">v</mi><mi mathvariant="normal">∥</mi></mrow><annotation encoding="application/x-tex">\| \boldsymbol{v} \|</annotation></semantics></math></span></span> ). Let <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">v</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{v}</annotation></semantics></math></span></span> denote the vector before normalization and <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi><mover accent="true"><mi mathvariant="bold-italic">v</mi><mo>^</mo></mover></mi></mrow><annotation encoding="application/x-tex">\boldsymbol{\hat{v}}</annotation></semantics></math></span></span> denote the normalized vector.</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi><mover accent="true"><mi mathvariant="bold-italic">v</mi><mo>^</mo></mover></mi><mo>=</mo><mfrac><mi mathvariant="bold-italic">v</mi><mrow><mi mathvariant="normal">∥</mi><mi mathvariant="bold-italic">v</mi><mi mathvariant="normal">∥</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\boldsymbol{\hat{v}} = \frac{\boldsymbol{v}}{ \| \boldsymbol{v} \| }</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using the Pythagorean theorem:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">∥</mi><mi mathvariant="bold-italic">v</mi><mi mathvariant="normal">∥</mi><mo>=</mo><msqrt><mrow><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup></mrow></msqrt></mrow><annotation encoding="application/x-tex">\| \boldsymbol{v} \| = \sqrt{v_x^2 + v_y^2 + v_z^2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi><mover accent="true"><mi mathvariant="bold-italic">v</mi><mo>^</mo></mover></mi><mo>=</mo><mi mathvariant="bold-italic">v</mi><mo>⋅</mo><mfrac><mn>1</mn><msqrt><mrow><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\boldsymbol{\hat{v}} = \boldsymbol{v} \cdot \frac{1}{\sqrt{v_x^2 + v_y^2 + v_z^2}}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can then break this down into each component (x, y, and z) of the 3-dimensional vector:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><msub><mi>v</mi><mi>x</mi></msub><mo>^</mo></mover><mo>=</mo><msub><mi>v</mi><mi>x</mi></msub><mo>⋅</mo><mfrac><mn>1</mn><msqrt><mrow><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\hat{v_x} = v_x \cdot \frac{1}{\sqrt{v_x^2 + v_y^2 + v_z^2}}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><msub><mi>v</mi><mi>y</mi></msub><mo>^</mo></mover><mo>=</mo><msub><mi>v</mi><mi>y</mi></msub><mo>⋅</mo><mfrac><mn>1</mn><msqrt><mrow><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\hat{v_y} = v_y \cdot \frac{1}{\sqrt{v_x^2 + v_y^2 + v_z^2}}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><msub><mi>v</mi><mi>z</mi></msub><mo>^</mo></mover><mo>=</mo><msub><mi>v</mi><mi>z</mi></msub><mo>⋅</mo><mfrac><mn>1</mn><msqrt><mrow><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\hat{v_z} = v_z \cdot \frac{1}{\sqrt{v_x^2 + v_y^2 + v_z^2}}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The addition and multiplication are easy enough to execute, but both the division and the square root would be very slow on a Quake III-era CPU. Thus, we need a faster method to return an approximate value of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{\sqrt{x}}</annotation></semantics></math></span></span>.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="ieee-754-how-computers-store-fractions">IEEE 754: How Computers Store Fractions</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s pretty straightforward to store an integer in binary by just converting it into base 2:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>0000010</mn><msub><mn>1</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">5_{10} = 0000 0101_2</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">But how do computers store fractions? One simple approach would be to put a decimal point at a fixed position in the number:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>0101.000</mn><msub><mn>0</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">5_{10} = 0101 . 0000_2</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>5.2</mn><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>0101.010</mn><msub><mn>0</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">5.25_{10} = 0101 . 0100_2</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This seems to work well, but we&#x27;ve drastically reduced the size of the numbers we can store. We only have half as many bits to store our integer part, which limits us to a relatively small range of numbers, and we only have half as many bits for our fractional part, which doesn&#x27;t give us a ton of precision.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Instead, we can borrow the idea of scientific notation, which represents numbers with a mantissa and exponent like <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>5.43</mn><mo>⋅</mo><mn>1</mn><msup><mn>0</mn><mn>3</mn></msup></mrow><annotation encoding="application/x-tex">5.43 \cdot 10^3</annotation></semantics></math></span></span>. With this, we can store very large values and very small values with a consistently low relative error. This is called &quot;floating point&quot;--unlike the prior idea, the decimal point can effectively &quot;float around&quot; in the bit representation to give us precision at both very small and very large values. Thus, if <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span> is the number we want to store, we write it in terms of exponent <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi></mrow><annotation encoding="application/x-tex">E</annotation></semantics></math></span></span> and mantissa <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span></span>:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>x</mi><mo>=</mo><mi>M</mi><mo>⋅</mo><msup><mn>2</mn><mi>E</mi></msup></mrow><annotation encoding="application/x-tex">x = M \cdot 2^E</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At the time Quake III was released, most CPUs were 32-bit. Thus, the floating point representation used 32 bits to store a number. They were allocated as follows:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">1 bit</th><th class="border border-black p-2 dark:border-white">8 bits</th><th class="border border-black p-2 dark:border-white">23 bits</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">sign</td><td class="border border-gray-500 p-2">exponent</td><td class="border border-gray-500 p-2">mantissa</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The &quot;sign&quot; bit is used to represent whether the number was positive or negative. In this case, since we know the argument to the square root function should always be positive, we can assume it to always be zero. There are also some special cases that happen for special values like <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">NaN</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">inf</code>, or very small numbers, which we can also ignore for now.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Note that the exponent could be positive or negative. To accommodate this, it is stored with an offset of 127. For example, to store <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>1.01</mn><mo>⋅</mo><msup><mn>2</mn><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">5_{10} = 1.01 \cdot 2^2</annotation></semantics></math></span></span>, we would take the exponent <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mn>2</mn><mn>10</mn></msub><mo>=</mo><mn>1</mn><msub><mn>0</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">2_{10} = 10_2</annotation></semantics></math></span></span> and add <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>12</mn><msub><mn>7</mn><mn>10</mn></msub><mo>=</mo><mn>0111111</mn><msub><mn>1</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">127_{10} = 0111 1111_2</annotation></semantics></math></span></span> to it to get <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1000000</mn><msub><mn>1</mn><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">1000 0001_2</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In scientific notation, the first digit of the mantissa must be between 1 and 9. In this &quot;binary scientific notation&quot;, the first digit must be between 1 and 1. (You can see this in our example: <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>1.01</mn><mo>⋅</mo><msup><mn>2</mn><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">5_{10} = 1.01 \cdot 2^2</annotation></semantics></math></span></span>.) Therefore, we don&#x27;t actually have to store it.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="interpreting-floats-as-ints">Interpreting floats as ints</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">An integer is just represented as a base 2 number. So, what happens if we take a number, find the bits of its floating-point representation, and then interpret those bits as a base 2 integer number?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Recall that our float is stored as:</p>
<table class="mx-auto border-separate border-spacing-0 overflow-hidden rounded-xl border-2 border-black dark:border-white"><thead><tr><th class="border border-black p-2 dark:border-white">1 bit</th><th class="border border-black p-2 dark:border-white">8 bits</th><th class="border border-black p-2 dark:border-white">23 bits</th></tr></thead><tbody><tr><td class="border border-gray-500 p-2">sign</td><td class="border border-gray-500 p-2">exponent representation</td><td class="border border-gray-500 p-2">mantissa representation</td></tr></tbody></table>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">(Note that the least significant bit is on the right.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Observe that the mantissa bits start in the ones place. However, remember that the mantissa is a fraction, so we&#x27;ve really stored <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mo>⋅</mo><msup><mn>2</mn><mn>23</mn></msup></mrow><annotation encoding="application/x-tex">(M - 1) \cdot 2^{23}</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The exponent bits, <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>E</mi><mo>+</mo><mn>127</mn></mrow><annotation encoding="application/x-tex">E + 127</annotation></semantics></math></span></span>, start in the <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>2</mn><mn>23</mn></msup></mrow><annotation encoding="application/x-tex">2^{23}</annotation></semantics></math></span></span> place.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Essentially, we have the sum of the quantity <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo><mo>⋅</mo><msup><mn>2</mn><mn>23</mn></msup></mrow><annotation encoding="application/x-tex">(M - 1) \cdot 2^{23}</annotation></semantics></math></span></span> and the quantity <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>E</mi><mo>+</mo><mn>127</mn><mo stretchy="false">)</mo><mo>⋅</mo><msup><mn>2</mn><mn>23</mn></msup></mrow><annotation encoding="application/x-tex">(E + 127) \cdot 2^{23}</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s define the function <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x)</annotation></semantics></math></span></span> that represents this bizarre operation of taking a floating-point representation and interpreting it as an integer:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msup><mn>2</mn><mn>23</mn></msup><mo>⋅</mo><mo stretchy="false">(</mo><mi>E</mi><mo>+</mo><mn>127</mn><mo>+</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x) = 2^{23} \cdot (E + 127 + M - 1)</annotation></semantics></math></span></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="an-observation-about-logarithms">An observation about logarithms</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s go back to our floating-point representation of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span>:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>x</mi><mo>=</mo><mi>M</mi><mo>⋅</mo><msup><mn>2</mn><mi>E</mi></msup></mrow><annotation encoding="application/x-tex">x = M \cdot 2^E</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, what happens if we take the logarithm? Let&#x27;s take the log base 2, since we&#x27;re working with binary. Evaluating <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(x)</annotation></semantics></math></span></span> directly would be pretty slow, but let&#x27;s proceed symbolically to see if we get anywhere useful.</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo>⋅</mo><msup><mn>2</mn><mi>E</mi></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(x) = \log_2(M \cdot 2^E)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo><mo>+</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><msup><mn>2</mn><mi>E</mi></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(x) = \log_2(M) + \log_2(2^E)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo><mo>+</mo><mi>E</mi></mrow><annotation encoding="application/x-tex">\log_2(x) = \log_2(M) + E</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Calculating <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(M)</annotation></semantics></math></span></span> would be hard. Instead, we can make do with an approximation. Remember that since <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>M</mi></mrow><annotation encoding="application/x-tex">M</annotation></semantics></math></span></span> is the mantissa, it will be greater than 1 but less than 2.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There&#x27;s a conveninent approximation we can use. Here&#x27;s a graph in Desmos:</p>
<div class="aspect-square max-w-6xl md:aspect-video"><iframe title="Desmos Graph" class="h-full w-full border-none" src="https://www.desmos.com/calculator/yvwmijecdr?embed" loading="lazy"></iframe></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The red curve is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(x)</annotation></semantics></math></span></span>, the function we want to approximate. The purple curve is the line <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">x - 1</annotation></semantics></math></span></span>, which is already a pretty good approximation. However, the blue curve is even better: <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi></mrow><annotation encoding="application/x-tex">x - 1 + \sigma</annotation></semantics></math></span></span>, where <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi></mrow><annotation encoding="application/x-tex">\sigma</annotation></semantics></math></span></span> is tuned for the maximum accuracy around <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mo>=</mo><mn>0.0450466</mn></mrow><annotation encoding="application/x-tex">\sigma = 0.0450466</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s continue, using <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>≈</mo><mi>x</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi></mrow><annotation encoding="application/x-tex">\log_2(x) \approx x - 1 + \sigma</annotation></semantics></math></span></span>:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>M</mi><mo stretchy="false">)</mo><mo>+</mo><mi>E</mi></mrow><annotation encoding="application/x-tex">\log_2(x) = \log_2(M) + E</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>≈</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi><mo>+</mo><mi>E</mi></mrow><annotation encoding="application/x-tex">\log_2(x) \approx M - 1 + \sigma + E</annotation></semantics></math></span></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="but-what-does-this-have-to-do-with-ixix">But what does this have to do with <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x)</annotation></semantics></math></span></span>?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Recall that</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msup><mn>2</mn><mn>23</mn></msup><mo>⋅</mo><mo stretchy="false">(</mo><mi>E</mi><mo>+</mo><mn>127</mn><mo>+</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x) = 2^{23} \cdot (E + 127 + M - 1)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We proceed:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msup><mn>2</mn><mn>23</mn></msup><mo>⋅</mo><mo stretchy="false">(</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi><mo>+</mo><mi>E</mi><mo>+</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x) = 2^{23} \cdot (M - 1 + \sigma + E + 127 - \sigma)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Observe the <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi><mo>+</mo><mi>E</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(M - 1 + \sigma + E)</annotation></semantics></math></span></span> part. Earlier, we found that <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>≈</mo><mi>M</mi><mo>−</mo><mn>1</mn><mo>+</mo><mi>σ</mi><mo>+</mo><mi>E</mi></mrow><annotation encoding="application/x-tex">\log_2(x) \approx M - 1 + \sigma + E</annotation></semantics></math></span></span>. Therefore, we can substitute this in:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>≈</mo><msup><mn>2</mn><mn>23</mn></msup><mo>⋅</mo><mo stretchy="false">(</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>+</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x) \approx 2^{23} \cdot (\log_2(x) + 127 - \sigma)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can solve for <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log_2(x)</annotation></semantics></math></span></span> as follows:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>≈</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>+</mo><mn>127</mn><mo>−</mo><mi>σ</mi></mrow><annotation encoding="application/x-tex">\frac{I(x)}{2^{23}} \approx \log_2(x) + 127 - \sigma</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi><mo>≈</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{I(x)}{2^{23}} - 127 + \sigma \approx \log_2(x)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>≈</mo><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi></mrow><annotation encoding="application/x-tex">\log_2(x) \approx \frac{I(x)}{2^{23}} - 127 + \sigma</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And there it is: we have a much faster way to compute the logarithm.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="rewrite-the-problem">Rewrite the problem</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Remember, we&#x27;re trying to find the quantity <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{\sqrt{x}}</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can use the properties of logarithms to find:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo>=</mo><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><msup><mi>x</mi><mrow><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac></mrow></msup><mo>=</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mi>x</mi></mrow><annotation encoding="application/x-tex">\log_2{\frac{1}{\sqrt{x}}} = \log_2{x^{-\frac{1}{2}}} = -\frac{1}{2} \log_2{x}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, let&#x27;s try substituting in our fast logarithm:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo>=</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mn>2</mn></msub><mi>x</mi></mrow><annotation encoding="application/x-tex">\log_2{\frac{1}{\sqrt{x}}} = -\frac{1}{2} \log_2{x}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi><mo>≈</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mrow><mo fence="true">[</mo><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\frac{I(\frac{1}{\sqrt{x}})}{2^{23}} - 127 + \sigma \approx -\frac{1}{2} \left[ \frac{I(x)}{2^{23}} - 127 + \sigma \right]</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can do some manipulation to solve for our result:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi><mo>≈</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mrow><mo fence="true">[</mo><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mn>127</mn><mo>+</mo><mi>σ</mi><mo fence="true">]</mo></mrow></mrow><annotation encoding="application/x-tex">\frac{I(\frac{1}{\sqrt{x}})}{2^{23}} - 127 + \sigma \approx -\frac{1}{2} \left[ \frac{I(x)}{2^{23}} - 127 + \sigma \right]</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>−</mo><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo>≈</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mo>⋅</mo><mfrac><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><msup><mn>2</mn><mn>23</mn></msup></mfrac><mo>+</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{I(\frac{1}{\sqrt{x}})}{2^{23}} - (127 - \sigma) \approx -\frac{1}{2} \cdot \frac{I(x)}{2^{23}} + \frac{1}{2} (127 - \sigma)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo><mo>−</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo>≈</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>+</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(\frac{1}{\sqrt{x}}) - 2^{23} (127 - \sigma) \approx -\frac{1}{2}  I(x) + \frac{1}{2} (2^{23} (127 - \sigma))</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo><mo>≈</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>+</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(\frac{1}{\sqrt{x}}) \approx -\frac{1}{2} I(x) + \frac{3}{2} (2^{23} (127 - \sigma))</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo stretchy="false">)</mo><mo>≈</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(\frac{1}{\sqrt{x}}) \approx \frac{3}{2} (2^{23} (127 - \sigma)) - \frac{1}{2} I(x)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo>≈</mo><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{1}{\sqrt{x}} \approx I^{-1}(\frac{3}{2} (2^{23} (127 - \sigma)) - \frac{1}{2} I(x))</annotation></semantics></math></span></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-magic-number">The magic number</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s look at this term:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>3</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mi>σ</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{3}{2} (2^{23} (127 - \sigma))</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We know everything here ahead of time. Why don&#x27;t we go through and calculate it?</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>σ</mi><mo>=</mo><mn>0.0450466</mn></mrow><annotation encoding="application/x-tex">\sigma = 0.0450466</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>3</mn><mn>2</mn></mfrac><mo stretchy="false">(</mo><msup><mn>2</mn><mn>23</mn></msup><mo stretchy="false">(</mo><mn>127</mn><mo>−</mo><mn>0.0450466</mn><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>=</mo><mn>1.5974630065963008</mn><mo>⋅</mo><mn>1</mn><msup><mn>0</mn><mn>9</mn></msup></mrow><annotation encoding="application/x-tex">\frac{3}{2} (2^{23} (127 - 0.0450466)) = 1.5974630065963008 \cdot 10^9</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>1.5974630065963008</mn><mo>⋅</mo><mn>1</mn><msup><mn>0</mn><mn>9</mn></msup><mo>≈</mo><mn>1597463007</mn></mrow><annotation encoding="application/x-tex">1.5974630065963008 \cdot 10^9 \approx 1597463007</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>159746300</mn><msub><mn>7</mn><mn>10</mn></msub><mo>=</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub></mrow><annotation encoding="application/x-tex">1597463007_{10} = 5F3759DF_{16}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So <em>there&#x27;s</em> where that&#x27;s from.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Anyway, we now have:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo>≈</mo><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{1}{\sqrt{x}} \approx I^{-1}(5F3759DF_{16} - \frac{1}{2} I(x))</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is the gist of how the function works. Let&#x27;s step through the code now.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-code">The Code</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s go through this code, line by line, to see how it matches up with our mathematical approximation.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">long</span><span class=""> i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">float</span><span class=""> x2</span><span class="" style="color:#ff218c">,</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">1.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	x2 </span><span class="" style="color:#8B5CF6">=</span><span class=""> number </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#8B5CF6">0.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> number</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">long</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">y</span><span class="" style="color:#ff218c">;</span><span class="">                       </span><span class="" style="color:#9CA3AF;font-style:italic">// evil floating point bit level hacking</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0x5f3759df</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> i </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">               </span><span class="" style="color:#9CA3AF;font-style:italic">// what the fuck?</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> x2 </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">   </span><span class="" style="color:#9CA3AF;font-style:italic">// 1st iteration</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">return</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A quick note: the argument to the function is called <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">number</code>, I&#x27;ll be calling it <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span> for simplicity&#x27;s sake.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="evil-floating-point-bit-level-hacking">Evil Floating Point Bit Level Hacking</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s look at the first part of the function.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">long</span><span class=""> i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">float</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> number</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">long</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">y</span><span class="" style="color:#ff218c">;</span><span class="">                       </span><span class="" style="color:#9CA3AF;font-style:italic">// evil floating point bit level hacking</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We start by declaring a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">long</code>, which is a 32-bit integer, called <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i</code>. Then, we declare a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">float</code>, or a floating-point representation number, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code>. We store the value of the argument (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">number</code>, or <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span>) into <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code>. Simple enough.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The next line, however, is where things get ugly. Starting from the right, let&#x27;s go step-by-step.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> is, of course, our floating-point number.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&amp;y</code> refers to the <em>reference</em> to <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code>--the location in computer memory at which <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> is stored. <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&amp;y</code> is a <em>pointer</em> to a floating-point number.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">( long * )</code> is a <em>cast</em>--it converts a value from one type to another. Here, we&#x27;re converting <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&amp;y</code> from &quot;pointer to a floating-point number&quot; to &quot;pointer to a 32-bit integer&quot;. This doesn&#x27;t modify the bits in <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> at all, it modifies the <em>type</em> of the pointer. It tells the compiler that the <em>value</em> at this pointer isn&#x27;t a float, it&#x27;s an int.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i = * [...]</code> will <em>dereference</em> the pointer. It sets <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i</code> equal to the value at that pointer. Since the pointer is considered a pointer to a 32-bit integer, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i</code> is declared as a 32-bit integer, this just sets <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i</code> equal to the bits at that location in memory.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Effectively, this part takes the bits in the floating-point representation of the argument (<code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">number</code>) and interprets them as a 32-bit integer instead.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Does that sound familiar? It&#x27;s our <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I(x)</annotation></semantics></math></span></span> function we defined earlier! These lines could be written as</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>←</mo><mi>x</mi></mrow><annotation encoding="application/x-tex">y \gets x</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mo>←</mo><mi>I</mi><mo stretchy="false">(</mo><mi>y</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">i \gets I(y)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Or, more concisely:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mo>←</mo><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">i \gets I(x)</annotation></semantics></math></span></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="whats-with-all-this-memory-trickery">What&#x27;s with all this memory trickery?</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You might ask, why can&#x27;t we just do this?</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="">i </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">long</span><span class="" style="color:#ff218c">)</span><span class=""> y</span><span class="" style="color:#ff218c">;</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After all, we just want <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> as an integer, right?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, this expression will actually convert the <em>value</em> that <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> represents into an integer. For instance, if <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>=</mo><mn>5.2</mn><msub><mn>5</mn><mn>10</mn></msub><mo>=</mo><mn>1.0101</mn><mo>⋅</mo><msup><mn>2</mn><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">y = 5.25_{10} = 1.0101 \cdot 2^2</annotation></semantics></math></span></span>, then this code will set <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>=</mo><mn>5</mn></mrow><annotation encoding="application/x-tex">i = 5</annotation></semantics></math></span></span>. It will <em>convert</em> <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> to an integer.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This isn&#x27;t what we want. We don&#x27;t want to do any conversion -- we are literally taking the bit representation of <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> and interpreting it as an integer instead. Thus, we convert the <em>pointer</em> instead, which doesn&#x27;t actually modify the bits stored in memory.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">You might think that the code looks ugly. That&#x27;s because it is--casting a pointer from one type to another is considered &quot;undefined behavior,&quot; and the C standard does not guarantee what will happen. We&#x27;re basically tricking the compiler here. This is definitely considered bad practice, which is why it&#x27;s <em>&quot;Evil Floating Point Bit Level Hacking&quot;</em>. <em>Evil</em> because it&#x27;s relying on undefined behavior, <em>Floating Point</em> because we&#x27;re working with a floating-point representation, <em>Bit Level</em> because we&#x27;re directly using the bit representation as an integer, and <em>Hacking</em> because this is most definitely not the way casting is intended to be used.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-wtf-line">The &quot;WTF&quot; Line</h3>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	i  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">0x5f3759df</span><span class=""> </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> i </span><span class="" style="color:#8B5CF6">&gt;&gt;</span><span class=""> </span><span class="" style="color:#8B5CF6">1</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">               </span><span class="" style="color:#9CA3AF;font-style:italic">// what the fuck?</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If you aren&#x27;t familiar with bitwise operations, the symbol <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">( i &gt;&gt; 1 )</code> might seem strange to you.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Think about doing a division problem like <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>13560</mn><mo>÷</mo><mn>3</mn></mrow><annotation encoding="application/x-tex">13560 \div 3</annotation></semantics></math></span></span>. It would be pretty tough, right? You&#x27;d probably need to write out the entire long division problem.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">On the other hand, think about finding <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>13560</mn><mo>÷</mo><mn>10</mn></mrow><annotation encoding="application/x-tex">13560 \div 10</annotation></semantics></math></span></span>. You can pretty quickly tell that the result is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1356</mn></mrow><annotation encoding="application/x-tex">1356</annotation></semantics></math></span></span>, right? All you had to do was shift all the digits one place to the right.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can use the same trick in binary. To divide a number by 2, we just need to shift each bit to the right. That is what <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&gt;&gt; 1</code> does--it&#x27;s just a much faster way to divide by two.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Remember, in the last step, we set <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>←</mo><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">i \gets I(x)</annotation></semantics></math></span></span>. Thus, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">( i &gt;&gt; 1 )</code> is <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{1}{2} I(x)</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We then subtract this from our magic number:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mo>←</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">i \gets 5F3759DF_{16} - \frac{1}{2} I(x)</annotation></semantics></math></span></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="finishing-up">Finishing up</h3>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#8B5CF6">&amp;</span><span class="">i</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This looks very similar to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i = * ( long * ) &amp;y;</code> line we looked at earlier, and that&#x27;s because it is. However, instead of interpreting the floating-point representation <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">y</code> as an integer, we&#x27;re interpreting the integer <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">i</code> as a floating-point representation. You can think of this as our <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I^{-1}(x)</annotation></semantics></math></span></span> function.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This step performs <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>←</mo><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mi>i</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y \gets I^{-1}(i)</annotation></semantics></math></span></span>. Since the last step set <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>←</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">i \gets 5F3759DF_{16} - \frac{1}{2} I(x)</annotation></semantics></math></span></span>, this effectively sets:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>←</mo><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y \gets I^{-1}(5F3759DF_{16} - \frac{1}{2} I(x))</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There we go!</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="but-wait-theres-more-newtons-method">But wait, there&#x27;s more: Newton&#x27;s method</h2>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">float</span><span class=""> x2</span><span class="" style="color:#ff218c">,</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">1.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	x2 </span><span class="" style="color:#8B5CF6">=</span><span class=""> number </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#8B5CF6">0.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> x2 </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">   </span><span class="" style="color:#9CA3AF;font-style:italic">// 1st iteration</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">What does this do?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This line performs &quot;Newton&#x27;s method&quot;, which is a method of refining an approximation for the root of a function.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s define <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mn>1</mn><msup><mi>t</mi><mn>2</mn></msup></mfrac><mo>−</mo><mi>x</mi></mrow><annotation encoding="application/x-tex">f(t) = \frac{1}{t^2} - x</annotation></semantics></math></span></span>. Notice that when <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{1}{\sqrt{x}}</annotation></semantics></math></span></span>, <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">f(t) = 0</annotation></semantics></math></span></span>. Therefore, we can use a root-finding algorithm to try to find this root of <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span></span>, and we&#x27;ll get back a better approximation for <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{1}{\sqrt{x}}</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s a graph that shows how Newton&#x27;s method works:</p>
<div class="aspect-square max-w-6xl md:aspect-video"><iframe title="Desmos Graph" class="h-full w-full border-none" src="https://www.desmos.com/calculator/ibkfxugxss?embed" loading="lazy"></iframe></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Note: since we&#x27;re working with both arguments to <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Q</mi><mi mathvariant="normal">_</mi><mi>r</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">Q\_rsqrt(x)</annotation></semantics></math></span></span> and arguments to <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span></span>, I&#x27;ve decided to stick with using <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span></span> for the latter. Generally, when working with Newton&#x27;s method, this value would be called <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We have our function in red, and an initial guess in dotted black, and we&#x27;re trying to find the point at which the function crosses the t-axis. We can draw a line tangent to the function at our initial guess (in green) and then find the intersection of that line with the t-axis to get an even better guess. We can keep doing this until we&#x27;re happy with the precision.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">So, we have our initial guess given by our <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>I</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo stretchy="false">(</mo><mn>5</mn><mi>F</mi><mn>3759</mn><mi>D</mi><msub><mi>F</mi><mn>16</mn></msub><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mi>I</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">I^{-1}(5F3759DF_{16} - \frac{1}{2} I(x)</annotation></semantics></math></span></span> expression. Let&#x27;s call this guess <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">t_0</annotation></semantics></math></span></span>. How do we draw a tangent line?</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Remember, the point-slope form for a line is given by <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>=</mo><mi>m</mi><mo stretchy="false">(</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo>+</mo><msub><mi>y</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">y = m(t - t_0) + y_0</annotation></semantics></math></span></span>. Thus, we can just plug in our initial point <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo separator="true">,</mo><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">(t_0, f(t_0))</annotation></semantics></math></span></span> to get <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>=</mo><mi>m</mi><mo stretchy="false">(</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo>+</mo><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y = m(t - t_0) + f(t_0)</annotation></semantics></math></span></span>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To get the slope <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>m</mi></mrow><annotation encoding="application/x-tex">m</annotation></semantics></math></span></span>, we need to take the derivative of the function <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span></span>. Let&#x27;s do this later--just call it <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f&#x27;(t)</annotation></semantics></math></span></span> for now.</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>=</mo><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>⋅</mo><mo stretchy="false">(</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo>+</mo><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y = f&#x27;(t) \cdot (t - t_0) + f(t_0)</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We&#x27;re trying to find the point where this tangent line crosses the t-axis, that is, where <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">y = 0</annotation></semantics></math></span></span>. Substitute <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span></span> for <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>y</mi></mrow><annotation encoding="application/x-tex">y</annotation></semantics></math></span></span>:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mn>0</mn><mo>=</mo><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>⋅</mo><mo stretchy="false">(</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo>+</mo><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">0 = f&#x27;(t) \cdot (t - t_0) + f(t_0)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo><mo>=</mo><mo>−</mo><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>⋅</mo><mo stretchy="false">(</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t_0) = - f&#x27;(t) \cdot (t - t_0)</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo>−</mo><mfrac><mrow><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow></mfrac><mo>=</mo><mi>t</mi><mo>−</mo><msub><mi>t</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">-\frac{f(t_0)}{f&#x27;(t_0)} = t - t_0</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><mfrac><mrow><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 - \frac{f(t_0)}{f&#x27;(t_0)}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">And we&#x27;ve arrived at the formula for Newton&#x27;s method.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s substitute in our <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(t)</annotation></semantics></math></span></span> and <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f&#x27;(t)</annotation></semantics></math></span></span>. First, we need to find the derivative:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mi>d</mi><mrow><mi>d</mi><mi>t</mi></mrow></mfrac><mo fence="false" stretchy="true" minsize="1.8em" maxsize="1.8em">[</mo><mfrac><mn>1</mn><msup><mi>t</mi><mn>2</mn></msup></mfrac><mo>−</mo><mi>x</mi><mo fence="false" stretchy="true" minsize="1.8em" maxsize="1.8em">]</mo><mo>=</mo><mfrac><mi>d</mi><mrow><mi>d</mi><mi>t</mi></mrow></mfrac><msup><mi>t</mi><mrow><mo>−</mo><mn>2</mn></mrow></msup><mo>=</mo><mo>−</mo><mn>2</mn><msup><mi>t</mi><mrow><mo>−</mo><mn>3</mn></mrow></msup></mrow><annotation encoding="application/x-tex">f&#x27;(t) = \frac{d}{dt} \Big[ \frac{1}{t^2} - x \Big] = \frac{d}{dt} t^{-2} = -2t^{-3}</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, proceed:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><mfrac><mrow><mi>f</mi><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow><mrow><msup><mi>f</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo stretchy="false">(</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 - \frac{f(t_0)}{f&#x27;(t_0)}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><mfrac><mrow><mfrac><mn>1</mn><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup></mfrac><mo>−</mo><mi>x</mi></mrow><mrow><mo>−</mo><mn>2</mn><msubsup><mi>t</mi><mn>0</mn><mrow><mo>−</mo><mn>3</mn></mrow></msubsup></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 - \frac{\frac{1}{t_0^2} - x}{-2t_0^{-3}}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><mfrac><mrow><mo stretchy="false">(</mo><mfrac><mn>1</mn><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup></mfrac><mo>−</mo><mi>x</mi><mo stretchy="false">)</mo><msubsup><mi>t</mi><mn>0</mn><mn>3</mn></msubsup></mrow><mrow><mo>−</mo><mn>2</mn></mrow></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 - \frac{(\frac{1}{t_0^2} - x)t_0^3}{-2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>+</mo><mfrac><mrow><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><msubsup><mi>t</mi><mn>0</mn><mn>3</mn></msubsup><mi>x</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 + \frac{t_0 - t_0^3x}{2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mrow><mn>2</mn><msub><mi>t</mi><mn>0</mn></msub><mo>+</mo><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><msubsup><mi>t</mi><mn>0</mn><mn>3</mn></msubsup><mi>x</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{2t_0 + t_0 - t_0^3x}{2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mrow><mn>3</mn><msub><mi>t</mi><mn>0</mn></msub><mo>−</mo><msubsup><mi>t</mi><mn>0</mn><mn>3</mn></msubsup><mi>x</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{3t_0 - t_0^3x}{2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><mfrac><mrow><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">(</mo><mn>3</mn><mo>−</mo><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup><mi>x</mi><mo stretchy="false">)</mo></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">t = \frac{t_0(3 - t_0^2x)}{2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo>⋅</mo><mfrac><mrow><mn>3</mn><mo>−</mo><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup><mi>x</mi></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">t = t_0 \cdot \frac{3 - t_0^2x}{2}</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">(</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo>−</mo><mfrac><mrow><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup><mi>x</mi></mrow><mn>2</mn></mfrac><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">t = t_0 ( \frac{3}{2} - \frac{t_0^2x}{2} )</annotation></semantics></math></span></div>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mo>=</mo><msub><mi>t</mi><mn>0</mn></msub><mo stretchy="false">(</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo>−</mo><mfrac><mi>x</mi><mn>2</mn></mfrac><msubsup><mi>t</mi><mn>0</mn><mn>2</mn></msubsup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">t = t_0 ( \frac{3}{2} - \frac{x}{2} t_0^2 )</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Let&#x27;s look at that line of code again:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">float</span><span class=""> x2</span><span class="" style="color:#ff218c">,</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">const</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">=</span><span class=""> </span><span class="" style="color:#8B5CF6">1.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">	x2 </span><span class="" style="color:#8B5CF6">=</span><span class=""> number </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#8B5CF6">0.5F</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	y  </span><span class="" style="color:#8B5CF6">=</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> threehalfs </span><span class="" style="color:#8B5CF6">-</span><span class=""> </span><span class="" style="color:#ff218c">(</span><span class=""> x2 </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#8B5CF6">*</span><span class=""> y </span><span class="" style="color:#ff218c">)</span><span class=""> </span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">;</span><span class="">   </span><span class="" style="color:#9CA3AF;font-style:italic">// 1st iteration</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#9CA3AF;font-style:italic">//	y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Earlier in the code, there is a line that performs <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mn>2</mn></msub><mo>←</mo><mi>x</mi><mo>⋅</mo><mn>0.5</mn></mrow><annotation encoding="application/x-tex">x_2 \gets x \cdot 0.5</annotation></semantics></math></span></span>. This is just so that <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>x</mi><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{x}{2}</annotation></semantics></math></span></span> doesn&#x27;t have to be recalculated on each iteration (even though the second iteration was since removed). Note that we multiply by 0.5 instead of dividing by 2 because multiplication is faster than division. Also, we can&#x27;t do the bit-shifting trick here since <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span></span> is a floating-point number, not an integer.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Newton&#x27;s iteration lines do:</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>←</mo><mi>y</mi><mo stretchy="false">(</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo>−</mo><mo stretchy="false">(</mo><msub><mi>x</mi><mn>2</mn></msub><mo>⋅</mo><mi>y</mi><mo>⋅</mo><mi>y</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y \gets y (\frac{3}{2} - ( x_2 \cdot y \cdot y ))</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">which is equivalent to</p>
<div class="math math-display"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>y</mi><mo>←</mo><mi>y</mi><mo stretchy="false">(</mo><mfrac><mn>3</mn><mn>2</mn></mfrac><mo>−</mo><mfrac><mi>x</mi><mn>2</mn></mfrac><msup><mi>y</mi><mn>2</mn></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y \gets y (\frac{3}{2} - \frac{x}{2} y^2 )</annotation></semantics></math></span></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">which is exactly the expression for Newton&#x27;s method we found earlier.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This line of code can be repeated to get better and better approximations. However, it appears the authors of Quake III decided that only one iteration was necessary, since the second one was removed.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-end">The End</h4>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">float</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">Q_rsqrt</span><span class="" style="color:#ff218c">(</span><span class=""> </span><span class="" style="color:#8B5CF6">float</span><span class=""> number </span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">{</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">.</span><span class=""></span></div><div class="" style="color:#404040"><span class="">	</span><span class="" style="color:#8B5CF6">return</span><span class=""> y</span><span class="" style="color:#ff218c">;</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#ff218c">}</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ll end with a quote from a relevant <a href="https://xkcd.com/664/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">xkcd</a>:</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Some engineer out there has solved P=NP and it&#x27;s locked up in an electric eggbeater calibration routine. For every 0x5f375a86 we learn about, there are thousands we never see.</p>
</blockquote>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">(It looks like the constant Randall mentioned was based on <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mo>=</mo><mn>0.0430357</mn></mrow><annotation encoding="application/x-tex">\sigma = 0.0430357</annotation></semantics></math></span></span>, not <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mo>=</mo><mn>0.0450466</mn></mrow><annotation encoding="application/x-tex">\sigma = 0.0450466</annotation></semantics></math></span></span>.)</p></div>]]></description>
            <link>https://breq.dev/2021/03/17/5F3759DF</link>
            <guid isPermaLink="false">/2021/03/17/5F3759DF</guid>
            <category><![CDATA[c++]]></category>
            <category><![CDATA[math]]></category>
            <pubDate>Wed, 17 Mar 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[How I Use Dokku]]></title>
            <description><![CDATA[<div class="e-content font-body"><p class="mx-auto my-4 max-w-prose font-body text-lg ">As I&#x27;ve started to explore cloud and microservices-based projects, I&#x27;ve turned to Dokku to host and manage my running projects. It&#x27;s become more or less my one-stop-shop for hosting all the projects I&#x27;ve worked on. A lot of my projects have different requirements, though, so I wanted to share the techniques and setup I use to keep everything running smoothly. This isn&#x27;t an exhaustive list of all the projects I host, but it chronicles the difficulties I&#x27;ve had over time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This isn&#x27;t going to be a How-To guide for Dokku--there are plenty of those already. It&#x27;s more of a collection of tips and tricks, and an explanation of which features I find most useful.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="what-is-dokku">What is Dokku?</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">With the growth in popularity of <a href="https://en.wikipedia.org/wiki/Microservices" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">microservices</a>, there has also been a growth in &quot;Platform-as-a-Service&quot; providers like <a href="https://www.heroku.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Heroku</a>. These platforms abstract away the details of running a Linux VM and installing dependencies, which drastically simplifies the process of deploying apps. However, these services can be expensive--for instance, once you burn through Heroku&#x27;s free tier, you&#x27;ll be charged $7 per month <em>per container</em>. This might be a fair price for businesses who need solid reliability and high performance, but it&#x27;s prohibitively expensive for a tinkerer who just wants to try out a new technology or work on a side project every so often.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Dokku is a <em>self-hosted</em> platform-as-a-service, and it can be installed on any Linux machine you have access to. It&#x27;s limited to only one machine, so all of your apps will need to fight over the system&#x27;s CPU/ram/etc, and there&#x27;s no easy way to scale across multiple servers. However, renting a single server is much cheaper than running many different containers on a retail PaaS. At time of writing, I&#x27;m running about 15 different projects, which would cost about <span class="math math-inline"><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>7</mn><mo>×</mo><mn>15</mn><mo>=</mo><mo>∗</mo><mo>∗</mo></mrow><annotation encoding="application/x-tex">7×15 = **</annotation></semantics></math></span></span>105** every month. That&#x27;s a lot more expensive than a $5 VPS.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-server">The Server</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My Dokku instance is currently being rented from Azure with credits I&#x27;ve been getting for free (it&#x27;s a long story). I run Ubuntu LTS because it&#x27;s stable, popular, and what I&#x27;m familiar with. I just used Dokku&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">bootstrap.sh</code> script to get started. Their <a href="http://dokku.viewdocs.io/dokku/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">docs</a> have a good installation guide.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I use Dokku&#x27;s <a href="https://github.com/dokku/dokku-letsencrypt" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">letsencrypt plugin</a> to manage HTTPS for all the apps. Handling this at the platform layer instead of the application layer makes applications easier to develop--I don&#x27;t have to worry about encryption for every single app, I can just configure it once in Dokku. Encryption is a necessity for me, as my website runs on a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.dev</code> domain and is thus on the HSTS preload list by default. (I&#x27;m glad that Google did this, and I&#x27;m happy to be part of the push for HTTPS adoption everywhere, but <em>boy</em> is working around it frustrating sometimes.)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="snowflake">Snowflake</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I&#x27;ve written about Snowflake <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/snowflake">here</a> before, but here&#x27;s the TL;DR: It&#x27;s a service for generating unique, time-ordered, 64-bit ID numbers. It&#x27;s built with Python and Flask.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Notably, it&#x27;s segmented into two distinct parts: &quot;Snowflake&quot; generates the IDs based on its instance number, and &quot;Snowcloud&quot; assigns the instance numbers to the Snowflake instances. This is for scalability reasons: it&#x27;s possible to scale the Snowflake app to many different instances, but as long as each has a unique instance number, the IDs will not conflict. Additionally, Redis is used to keep track of the in-use instance numbers.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Deploying Flask apps to Dokku is easy--no special buildpacks are required, and the Procfile is simply <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">web: gunicorn app:app</code>. Adding Redis is also pretty straightforward using Dokku&#x27;s <a href="https://github.com/dokku/dokku-redis" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">official Redis plugin</a>. When you create a Redis instance and link it to your app, Dokku will set the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">REDIS_URL</code> environment variable in your app to point to that Redis instance. For testing locally, I just put <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">export REDIS_URL=redis://localhost:6379</code> in my <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">.env</code> so that my test and production environments are similar.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Snowcloud app will only assign instance IDs to processes with the proper API key. Thus, this key needs to be set in both the Snowcloud and Snowflake app. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku config</code> command is your friend here: I just set them as environment variables using <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku config:set SNOWCLOUD-KEY={key}</code>. Being able to set secrets like this in environment variables is really handy--it&#x27;s great to keep them out of the repo (especially because I like to share my code on my GitHub), and it&#x27;s a lot easier than trying to store them in a file or something.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="autoredditor">AutoRedditor</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">AutoRedditor is a project I made to quickly return random Reddit posts on demand. It has two main parts: a worker thread to retrieve Reddit posts and store them in Redis, and a web thread to retrieve posts from Redis when a request is received. It&#x27;s built with Python, but using <a href="https://docs.python.org/3/library/asyncio.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">asyncio</a>, mostly because I wanted an excuse to learn the technology.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Running the worker thread was as simple as adding <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">worker: python3 worker.py</code> to the Procfile. However, running the web thread was a bit more difficult, as I&#x27;m using <a href="https://pgjones.gitlab.io/quart/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Quart</a> instead of Flask here. Typically, deploying a Quart app is just as simple as replacing Gunicorn with Hypercorn, but it&#x27;s a bit trickier here. Dokku apps need to respect the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">PORT</code> environment variable and make their web interface available on that port. The solution is to explicitly specify the port variable in the command: <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">web: hypercorn -b 0.0.0.0:${PORT} app:app</code>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="cards">Cards</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/cards">Cards</a> is a service to generate image or HTML-based cards for embedding into websites. It uses <a href="https://github.com/pyppeteer/pyppeteer" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Pyppeteer</a>, a Python port of the popular Puppeteer JS library used for automating actions in a headless web browser. Specifically, Pyppeteer is used to render and screenshot HTML-based cards in order to produce images. (I experimented with using Selenium, but I found Pyppeteer was easier to install and use.) Because Pyppeteer is based on asyncio, I decided to go with Quart (+ Hypercorn) for this project as well. Getting Pyppeteer to run in an app container isn&#x27;t particularly straightforward, as it requires a Chromium install to use for the browser.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Thankfully, Heroku has published a <a href="https://github.com/heroku/heroku-buildpack-google-chrome" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Google Chrome buildpack</a> that can be used to install Chrome into the app. Running <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku buildpacks:add cards https://github.com/heroku/heroku-buildpack-google-chrome</code> and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku buildpacks:add cards https://github.com/heroku/heroku-buildpack-python</code> will configure the app to install both Chrome and a Python runtime. Heroku&#x27;s buildpack sets the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">GOOGLE_CHROME_SHIM</code> environment variable, and this just has to be passed to Pyppeteer&#x27;s <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">launch(executablePath)</code> function. For local testing, leave this variable unset, and Pyppeteer will just use your local Chrome install.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Cards will also cache the screenshots so that it doesn&#x27;t have to run Pyppeteer for every request. To do this, I needed to mount some sort of persistent storage to the running container. The <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku docker-options</code> command was perfect for this: I just needed to add <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-v /home/breq/cards:/storage</code> to the deploy options.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="emoji">Emoji</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I made a simple emoji keyboard at <a href="https://emoji.breq.dev/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">emoji.breq.dev</a> because I was frustrated that I couldn&#x27;t send emoji from my computer with Google Voice. I used Jekyll, which is overkill for a single-page site, but I wanted to use my existing website theme and avoid repeating the same code over and over for every single emoji. I wanted to keep my built <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">_site</code> folder out of the repo to avoid cluttering things up, which complicated Dokku deployment a bit.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I needed to find a way to make Dokku build the site when I deployed. I found some buildpacks that worked: Heroku&#x27;s <a href="https://github.com/heroku/heroku-buildpack-nginx.git" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nginx pack</a> and <a href="https://github.com/inket/dokku-buildpack-jekyll3-nginx.git" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">inket&#x27;s Jekyll pack</a>. These were surprisingly painless--the Jekyll pack tells Dokku how to install Ruby, run Jekyll to build the site, and point nginx to the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">_site</code> folder.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="breqbot">Breqbot</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/breqbot">Breqbot</a> is a Discord bot I built. It uses the traditional Gateway API instead of the newer Interactions one, so most functions are handled by a worker thread that connects to Discord over a WebSocket. However, some information is available over a REST API, so there&#x27;s a web thread that runs as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The most difficult part about getting Breqbot to work was the voice features. Breqbot includes a soundboard feature to play sound in a Discord voice channel. Handling these audio codecs requires installing quite a few packages: I ended up using <a href="https://github.com/heroku/heroku-buildpack-apt.git" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Heroku&#x27;s Apt buildpack</a> to install <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">libffi-dev</code>, <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">libnacl-dev</code>, and <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">libopus-dev</code>, and I used <a href="https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">jonathanong&#x27;s ffmpeg buildpack</a> for, well, ffmpeg.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="minecraft">Minecraft</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to host a Minecraft server on my cloud VPS as well, so I decided to try to use Dokku to manage it. So far, it&#x27;s been working out pretty well, and it&#x27;s nice to have everything managed in one place. However, getting Minecraft to work initially in a containerized setup wasn&#x27;t straightforward.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I found <a href="https://github.com/itzg/docker-minecraft-server/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">itzg&#x27;s Docker Minecraft server image</a> and set about making it work with Dokku. Dokku does support deploying from a Docker image, and although the process isn&#x27;t particularly straightforward, it is at least <a href="http://dokku.viewdocs.io/dokku/deployment/methods/images/#deploying-from-a-docker-registry" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">well documented</a>.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, I set the docker options:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-e TYPE=PAPER</code>, to run a high-performance Paper server instead of the default vanilla one</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-p 25565:25565</code>, to expose the Minecraft server port to the Dokku host&#x27;s public IP address</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-v /home/breq/minecraft:/data</code>, to configure a persistant place to store the world data, plugins, datapacks, etc</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I needed a way to access the server console to run commands while the server is running. I found <a href="https://github.com/mesacarlos/WebConsole" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">mesacarlos&#x27; WebConsole plugin</a>, which can provide a password-protected console over the Internet. To expose this console, I used <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku proxy</code> to proxy ports 80 and 443 on the host to port 8080 inside the container. I&#x27;m currently hosting the web interface in a separate Dokku app. I just made sure to set the WebConsole port to 443 in the interface to connect to the container using HTTPS.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="syncthing">Syncthing</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I run a <a href="https://syncthing.net/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Syncthing</a> instance to sync files between my desktop and laptop, so I decided to try to run this through Dokku as well. Syncthing provides an official Docker image, so I didn&#x27;t have to use a third-party one, and they have good <a href="https://github.com/syncthing/syncthing/blob/main/README-Docker.md" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">documentation</a> as well. The only change I made from the guide was to proxy the web GUI through Dokku with <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">dokku proxy:ports-add syncthing https:443:8384</code> instead of exposing it directly to the Internet.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="wireguard">Wireguard</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I also decided to run a VPN, and I chose Wireguard because it seemed simple, well-supported, and lightweight. The <a href="https://linuxserver.io" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">LinuxServer.io</a> team maintains a Wireguard <a href="https://hub.docker.com/r/linuxserver/wireguard" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">image</a>, so I just needed to deploy it to a Dokku app. Using the documentation as a reference, I set these docker options:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--cap-add=NET_ADMIN --cap-add=SYS_MODULE</code> as Wireguard runs as a Linux kernel module, which can&#x27;t be containerized, so access to the kernel modules and network have to be granted</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-e TZ=America/New_York</code> to set the timezone</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-e SERVERURL=vpn.breq.dev</code> to set the server URL</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-p 51820:51820/udp</code> to expose the VPN port to the Internet</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-v /home/breq/vpn:/config</code> to mount the configuration files as a volume--this lets you grab the config files to distribute to the peer computers</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-v /lib/modules:/lib/modules</code> to mount the kernel modules directory, as Wireguard runs as a Linux kernel module</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">--sysctl=net.ipv4.conf.all.src_valid_mark=1</code> to allow routing all traffic through the VPN</li>
<li class="my-2 pl-2"><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">-e PEERS=breq-desk,breq-laptop,breq-phone</code> to define the peers I want to connect</li>
</ul></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="conclusion">Conclusion</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Running everything all in one place has really simplified things a lot. Being able to deploy apps quickly is really nice for prototyping ideas. Overall, I&#x27;m really glad I decided to start using Dokku to manage all the services I&#x27;m hosting in the cloud.</p></div>]]></description>
            <link>https://breq.dev/2021/02/10/dokku</link>
            <guid isPermaLink="false">/2021/02/10/dokku</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[networking]]></category>
            <pubDate>Wed, 10 Feb 2021 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Snowflake]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This is a service to generate Snowflake IDs. These are unique, time-ordered IDs, useful for things like instant messages or posts.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to get more experience working with the <a href="https://12factor.net/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">12 Factor App</a> model. Implementing a basic service like this seemed like the easiest way to go about it. I also figured this kind of service could be useful for me at some point.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The &quot;Snowflake&quot; ID format was designed at Twitter for identifying tweets, so its scalability is its main selling point. Each Snowflake is a 64-bit integer composed of a timestamp in milliseconds (42 bits), worker ID (10 bits), and increment (12 bits).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The format can support 1024 possible worker IDs -- too many to hardcode by hand, but too few to assign randomly. So, I made a second app, &quot;SnowCloud,&quot; that handles the assignment of these worker IDs to the Snowflake server instances. When a Snowflake server comes online, it will request a worker ID from a SnowCloud server. Then, it will periodically renew the worker ID with the SnowCloud server. The pool of worker IDs are stored as a Redis set, sorted by how recently each was used. In addition, each Snowflake server is identified by a UUID, and these UUIDs are tracked to ensure Snowflake servers can only renew their own assignments. (For reference, the SnowCloud repo is at, predictably, <a href="https://github.com/breqdev/snowcloud" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">breqdev/snowcloud</a>.)</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It does everything I set out to accomplish, and I got more experience developing microservices, which was cool. This was just a quick afternoon project--it was cool to go from idea to finished product in just a couple hours.</p></div>]]></description>
            <link>https://breq.dev/projects/snowflake</link>
            <guid isPermaLink="false">/projects/snowflake</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[redis]]></category>
            <pubDate>Fri, 04 Dec 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[STMusic]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="max-w-2xl mx-auto bg-red-100 rounded-xl my-4 p-2"><p class="mx-auto my-4 max-w-prose font-body text-lg ">Note that unlike other projects on this site, I wrote this for a homework assignment. That being said, adding in the speaker and music were things I did because I wanted to have fun, not to meet the assignment requirements.</p></div>
<div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/_AXSp7ZT-E8/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/_AXSp7ZT-E8/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Here&#x27;s a video I recorded demonstrating the game.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This game is similar to Guitar Hero or other rhythm-based games. The player should push the &quot;USER&quot; button whenever one of the scrolling indicators reaches the left of the screen. This was the first major project I made using C. The biggest challenge in this project was figuring out how to store and generate the sounds that made up each song.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a homework assignment in my Computer Organization class to help us learn how to use bitwise operations to display a game on the ST Discovery&#x27;s LCD. I chose to add sound functionality mostly because I thought it would be an interesting challenge, I was interested in how basic microcontrollers generate different sounds, and I figured a silent game would be rather boring.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="sound-generation">Sound Generation</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The STM32 microcontroller this project used doesn&#x27;t have any purpose-built hardware for generating sounds (that I&#x27;m aware of). So, the solution I settled on was to manually generate a square wave by setting a GPIO pin high, waiting for half the length of the waveform, setting it low, and waiting for the rest of the waveform.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The biggest hurdle with this approach was accurate timing. The STM32 can use interrupts to delay for a precise number of milliseconds, but generating square waves at specific frequencies requires sub-millisecond precision. The solution I came up with was to calibrate a busy-wait loop when the code begins using the millisecond timer, then use that busy-wait loop for sub-millisecond-precision delays.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This yielded a decent-sounding square wave, but the game audio still felt incomplete.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I attempted to play multiple notes at once by summing the square waves, but the result did not sound very good. Additionally, the timing code required to play two separate frequencies at once quickly became complicated. Perhaps I could have used two separate GPIO pins and a voltage divider to effectively increase the bit depth (allowing for 4 separate voltage levels to be sent to the speaker).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Instead of attempting that, I decided to try adding drum sounds. By playing each drum sound and then quickly switching to playing the melodic note, the device can give the illusion that both sounds are playing at once. This didn&#x27;t work out as well as I had hoped, but it sounded okay at least.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the kick drum, I borrowed a <a href="https://www.youtube.com/watch?v=Jd6nyynuzio" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">trick</a> used frequently by composers for the NES: By doing a rapid downward pitch bend in between melodic sections, it&#x27;s possible to fake a kick drum sound somewhat convincingly. Because I don&#x27;t have the luxury of a triangle-wave channel, this doesn&#x27;t sound as good in my project as it does in NES games, but the trick still works.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For the snare drum, I decided to just use a burst of white noise. But as the STM32 doesn&#x27;t have any built-in random number generation, I had to choose a pseudorandom algorithm to implement.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At first, I tried to use a Linear Congruential Generator, because it seemed easier to implement. However, with the parameters I chose, the period was small enough that I could hear a distinct tone in the output. I could have probably eliminated this by choosing better parameters, but I didn&#x27;t want to spent a bunch of time tuning the parameters.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I then looked into using a Mersenne Twister, because it seemed like a popular choice. I ultimately decided it against it as it seemed hard to implement. I also worried that it might be too slow, considering I&#x27;d want to be sending bits to the GPIO pin as fast as possible to ensure the snare sound had enough high-end frequencies.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I settled on XorShift, which was fast and had a basic implementation.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="data-packing">Data Packing</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">After figuring out how to synthesize the song, I needed to figure out how to store it. The trial version of CrossWorks Studio that I was using restricted me to a 16kB code size. I initially wanted to include multiple long songs (although I later scrapped this due to time constraints), so I needed to find an efficient way to store each note, drum, and indicator on the screen.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided early on to try to fit the information for each beat into a small integer and store these integers in an array. I looked into what information I would need to store:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Note pitch (7 bits when stored as MIDI note number)</li>
<li class="my-2 pl-2">Drum sound (2 bits - kick, drum, or neither)</li>
<li class="my-2 pl-2">Indicator (1 bit)</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">To store each note pitch, I decided to use MIDI note numbers. These only use 7 bits per note, and they can be converted to frequencies using a basic formula, so this was a much better solution than trying to store the note frequency or wavelength.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">10 bits is kind of an odd size, so I tried to figure out what else I could include to use all bits in a 16 bit integer. The first thing I added was duty cycle controls. The original NES had 3 duty cycle settings, and composers could create interesting <a href="https://www.youtube.com/watch?v=kl9v8gtYRZ4" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">effects</a> by switching between them. I decided to add 4 duty cycle settings to this project, although they didn&#x27;t sound as different as I had hoped (likely due to the poor quality speaker I used). This brought the total up to 12 bits.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I came up with the idea of including a &quot;message length&quot; field which would specify how many beats after this one were to be held out. This could drastically compress the resulting array by removing duplicate entries. I made this 4 bits long, allowing for up to 16-beat messages.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here&#x27;s the spreadsheet I built to pack these messages together for me. On the left, you can set the parameters of each note: is a marker shown on the display? what specific note is played? for how long? is there a drum sound? etc. On the right is each section packed into a single 16-bit integer. An array of these integers can be included with the game code to play back the song. By switching the tab at the bottom, you can see both of the songs I included with the game.</p>
<iframe class="w-full h-96" src="https://docs.google.com/spreadsheets/d/e/2PACX-1vQ2jTL6TOiYkK7ZLyM8OinKNpnfOwafpIabo_0DhFtii-M3KLkS-VDod56g5RjTcI22kW2fR8Yx7kno/pubhtml?widget=true&amp;headers=false"></iframe>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I had fun working on this, and I learned a lot about programming for embedded systems. The results didn&#x27;t sound spectacular, but they still enhanced the final game in my opinion.</p></div>]]></description>
            <link>https://breq.dev/projects/stmusic</link>
            <guid isPermaLink="false">/projects/stmusic</guid>
            <category><![CDATA[c++]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Fri, 09 Oct 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[BlockChat]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1203" height="751" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fblockchat.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Fblockchat.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fblockchat.png&amp;w=3840&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="description">Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">BlockChat is a simple decentralized chatroom application that uses a blockchain to store the message data.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project was something I quickly threw together in order to better understand blockchain while the Bitcoin boom was taking place. I couldn&#x27;t find a resource that explained proof-of-work, consensus, and other blockchain-related topics well enough for me to understand them, so I decided to try making my own blockchain application, loosely based around a couple of examples I saw around the Internet.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At its core, a blockchain is a distributed, public ledger, so I decided that the easiest blockchain application to write would be a chatroom. At the time, it seemed much easier than a digital token or currency system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started by implementing the blockchain data structure. As the name implies, this is a chain of blocks, where each new block contains the cryptographic hash of the previous block, so that old data cannot be modified without also re-writing all of the new data.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Then, I moved on to the proof-of-work algorithm. This algorithm is designed to be computationally expensive so that new blocks take a significant amount of time to &quot;mine&quot;. Because of this, if somebody wanted to re-write a previous part of the blockchain, it would take a prohibitively large amount of computing power to then re-write all of the blocks that follow.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For my proof-of-work algorithm, I decided to require finding an integer with a hash that starts with a certain number. Solutions to this are difficult to find (as miners have to blindly guess at what the number is) but easy to verify (just hash the potential number and see if the first digits match). To make sure no miner tries to re-use the same solution multiple times, I required each new solution to be a larger number than the previous one.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I didn&#x27;t put too much effort into optimizing my miner implementation - it only uses a single CPU thread, sequentially searching numbers. In an actual blockchain scenario, this isn&#x27;t a good choice, as anyone who could write a better miner (by using multiple CPU cores, GPUs, or even FPGAs) would then have much more computational power compared to the rest of the network. I also didn&#x27;t make the difficulty of the proof-of-work change in response to miner availability. This also isn&#x27;t a good idea for a real blockchain, as periods of low miner activity may not be able to mine new blocks quickly. Worse, if many more miners decided to mine my blockchain, a too-easy proof-of-work would make it easier for malicious miners to modify the blockchain.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Next, I worked on the consensus algorithm. For a node to accept new blocks, it first needs to verify the proof-of-work values and hashes to ensure the chain it receives is valid. Assuming it is, the node needs to choose which version of the chain it should prefer. In my implementation, the node will simply choose the longest version.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Finally, I needed to devise the protocol which would allow nodes to add messages to the blockchain and compare versions with other nodes. I decided to use a REST API, because it was the easiest solution.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the end, the program did work. However, there were a few corners I cut that made the end result kind of impractical.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first issue was that I did not include distinction between nodes and miners. In actual blockchains, nodes that want to put data on the blockchain will send that data to many different miners, so that their data would likely be included in the next block, regardless of which miner mined it. In my demo, in order for anybody to send a message to the chatroom, they have to be the one to mine the next block. If the blockchain is scaled beyond just a few miners, the chances of this happening would be nearly zero, so actually using the chatroom would be almost impossible.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The second issue is the lack of reward for miners. Initially, this was one of the things I didn&#x27;t intend to include, because I wanted to keep things simple. But because the miners have no incentive to mine blocks, it would be difficult to get benevolent miners to participate in the blockchain, and it would be easy for &quot;evil&quot; miners to gain enough of a share of the computing power to undermine the stability provided by proof-of-work.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">While the end result wasn&#x27;t something practical to deploy, working on this project definitely helped me understand the underlying design of blockchain platforms. It also answered many questions I had about the technology, such as &quot;Why do non-currency applications like <a href="https://en.wikipedia.org/wiki/Namecoin" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Namecoin</a> still have tradeable tokens?&quot; and &quot;How exactly does proof-of-work ensure the stability of the blockchain system?&quot;, so I would definitely consider it a success.</p></div>]]></description>
            <link>https://breq.dev/projects/blockchat</link>
            <guid isPermaLink="false">/projects/blockchat</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[python]]></category>
            <pubDate>Fri, 09 Oct 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Pinewood Derby Car]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/X23kVwWfueI/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/X23kVwWfueI/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A quick video of the car in action.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I was a smol child in cub scouts, we had a competition to see who could build the fastest car. I didn&#x27;t really care about that, so I decided to try and make something cool instead using an Arduino I had recently gotten.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Almost everything is wired directly up to the Arduino, except the LED matrix, which runs off of the I2C bus. I modified some example code to display an animation of flames scrolling across (that I had drawn up on some graph paper).</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I think I won an award for it? (To be honest it&#x27;s been so long that I can&#x27;t remember well). But I do remember the battery dying - I had used a small garage-door-opener battery in order to fit within the weight limit, and that ended up dying pretty quickly.</p></div>]]></description>
            <link>https://breq.dev/projects/pinewood-derby-car</link>
            <guid isPermaLink="false">/projects/pinewood-derby-car</guid>
            <category><![CDATA[arduino]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Fri, 09 Oct 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Breqbot]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="317" height="120" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2F8ball.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2F8ball.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2F8ball.png&amp;w=640&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">One of the many &quot;just for fun&quot; commands that Breqbot has.</p>
<blockquote class="-my-1 mx-auto max-w-3xl border-l-8 border-gray-400 py-1 pl-4 italic" style="max-width:min(max-content, 100%)">
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A previous version of this post had a link to invite the bot to your server. I have removed this link for a few reasons:</p>
<div class="mx-auto max-w-prose text-lg"><ol class="ml-8 list-decimal">
<li class="my-2 pl-2">The bot was built for a small set of friends half a decade ago when myself and my friend group both were very different from how they are now</li>
<li class="my-2 pl-2">I have no interest or motivation in keeping this bot running</li>
<li class="my-2 pl-2">While I remain proud of the work I put into this bot, it provides no particular utility compared to other bots out there</li>
</ol></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The GitHub repo will remain accessible.</p>
</blockquote>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Breqbot is a Discord bot that manages a virtual economy, provides several fun minigames, gives access to comics, and has a variety of other features. Users can add Breqbot to a Discord server with their friends, giving their community access to these features.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A couple things came together to make this project happen. First, one of my friends invited me to a Discord server where they were using a variety of other popular Discord bots. I wasn&#x27;t a fan of how some of these bots ran their economy or other features, and I wanted to see if I could implement something better. Second, my work on <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/mcstatus">McStatus</a>&#x27;s backend using Heroku gave me experience working with microservice architectures, and I wanted to work on a more complex microservices project.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A running Breqbot instance consists of three containerized processes: the web process, the Discord worker process, and the <a href="https://redis.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Redis</a> instance.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-discord-worker">The Discord Worker</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The discord worker is written using <a href="https://github.com/Rapptz/discord.py/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">discord.py</a> using its <a href="https://discordpy.readthedocs.io/en/latest/ext/commands/index.html" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">bot commands framework</a>. This process listens for Discord events over the Discord Gateway, selects an appropriate command, and executes it. Here are some examples of these commands:</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="303" height="318" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2F2048.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2F2048.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2F2048.png&amp;w=640&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By adding reactions to the message (the arrow emojis), members of the Discord server can work together to play the famous browser game &quot;2048.&quot; Breqbot will listen for these reactions, modify the internal game state, and update the game board displayed.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="479" height="313" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fprofile.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fprofile.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fprofile.png&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Breqbot allows a user to configure their profile, which can then be displayed on request by other users. These profiles are images drawn using <a href="https://pillow.readthedocs.io/en/stable/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Pillow</a>, a Python library for image manipulation.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="565" height="257" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fvex.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fvex.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fvex.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Breqbot can pull Vex Robotics Competition data from <a href="https://vexdb.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">VexDB</a> and create a summary of a team&#x27;s performance. Here&#x27;s my team from 2019-2020.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="593" height="332" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fxkcd.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fxkcd.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fxkcd.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Breqbot can share comics from a few series including the famous <a href="https://xkcd.com/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">xkcd</a>. Additionally, a worker task will continuously monitor these comics for updates, and it can be configured to automatically post them to certain channels.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="590" height="169" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fsoundboard.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fsoundboard.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fsoundboard.png&amp;w=1200&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Soundboard app lets server members add sounds (in the form of YouTube links) and corresponding emoji. Then, if someone reacts to the soundboard using that emoji, the corresponding sound will play in the server&#x27;s voice channel.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="384" height="210" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Froles.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Froles.png&amp;w=828&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Froles.png&amp;w=828&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Discord has a powerful &quot;Roles&quot; feature to help identify members of a server, but it does not have a way for users to assign themselves roles. Breqbot provides a &quot;reaction roles&quot; menu system - by reacting to this message, a user can select which roles they would like to receive, and Breqbot will automatically modify their roles as necessary.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-redis-instance">The Redis Instance</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most of Breqbot&#x27;s features rely on <a href="https://redis.io/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Redis</a> to store user data. For example, the role menu will store the set of messages it needs to watch reactions for, and the profile feature will store a user&#x27;s configuration in Redis. This allows the Discord worker to be restarted at any time with minimal disruption.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="449" height="424" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Freddit.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Freddit.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Freddit.png&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">One notable use of Redis is to cache Reddit posts. Breqbot uses <a href="https://praw.readthedocs.io/en/latest/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">PRAW</a> to automatically retrieve popular posts from Reddit to display. However, the Reddit API is slow, and it does not provide a method to choose a random popular post. Because of this, Breqbot uses a background task to periodically retrieve the 100 most popular posts from a variety of subreddits and store them in Redis. Then, when a user requests a post from one of these subreddits, Breqbot can retrieve it from its cache.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="the-web-process">The Web Process</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="948" height="942" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fwebsite.png&amp;w=1080&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fwebsite.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fwebsite.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The web process runs Breqbot&#x27;s accompanying website. If a Discord server chooses to enable it, Breqbot can publish information about that server&#x27;s economy and its members to a website URL. The web process will use Redis to get this information - it does not communicate directly with the Discord worker.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="612" height="419" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Fbreqbot%2Fportal.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Fbreqbot%2Fportal.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Fbreqbot%2Fportal.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Portal API allows other programs to connect to a Breqbot instance to provide additional functionality. In this example, the Portal (which was hosted on my laptop) will echo back any input it receives. A more practical use of this would be to build functionality that interacts with the real world, such as a remote-control robot that communicates over Discord.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Portal clients connect to Breqbot using WebSockets, which are handled by the web process. Requests and responses are sent between the Discord and Web processes through a Redis pub/sub channel. Management of Portal clients, including distribution of API keys, is handled by the Discord worker: if a user wants to register a new portal, they will receive their API keys through a direct message on Discord.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was my first project that ended up being used by such a wide audience: many of my friends added it to their communities on Discord, and people were constantly requesting functionality or finding bugs. I really enjoyed the experience of developing this and using it with my friends, though, and it was rewarding to see other people enjoying my work.</p></div>]]></description>
            <link>https://breq.dev/projects/breqbot</link>
            <guid isPermaLink="false">/projects/breqbot</guid>
            <category><![CDATA[discord]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[redis]]></category>
            <pubDate>Thu, 08 Oct 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[LPS System]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/4wxjDMXGJJ0/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/4wxjDMXGJJ0/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A quick demonstration of the system in action.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The LPS (Local Positioning System) is a system to help robots determine their location in a predefined area. This system uses a camera and colored markers to determine the position of robots and other objects in a scene, then relays that information to robots over a network connection.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Localization in robotics is a difficult but important problem. Through the Vex Robotics Competition, I’ve had experience trying to get a robot to perform pre-programmed movements. I’ve also worked on several automated robots outside of VRC. In all cases, I’ve been dissatisfied with the poor options available for determining a robot’s position.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="current-systems">Current Systems</h3>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="accelerometer">Accelerometer</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because an accelerometer measures acceleration, it must be double-integrated to track the robot’s position. This means that any error will drastically affect the result over time, and the calculated position will rapidly lose precision.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="encoder-wheels">Encoder Wheels</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By placing encoders on wheels, a robot can track its movement over time. VRC team 5225A, “πlons,” used a technique with three separate encoders to track the robot’s movement, its slip sideways, and its orientation (described here). However, any error (wheel slop, robot getting bumped or tipping, etc) will accumulate over time with this method. πlons needed to supplement their tracking system by occasionally driving into the corners of the VRC field in order to reset the accumulated error.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="global-positioning-system">Global Positioning System</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">GPS can work well for tracking a robot’s general position in a large area. However, common receivers have an accuracy of only about 3 meters, and while modules with centimeter-level accuracy are available, they are prohibitively expensive.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="1920" height="1080" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fpattern_wall.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fpattern_wall.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fpattern_wall.png&amp;w=3840&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">The &quot;Vex GPS&quot;: An example of the &quot;Pattern Wall&quot; approach to localization.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="pattern-wall">Pattern Wall</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">By placing a specific code along the walls of the area, a camera mounted on a robot can determine its position and orientation. This method will be used in the Vex AI Competition. Other objects in the field can block the robot’s view of the pattern wall. Additionally, this system requires a defined playing field with straight sides and walls, and each robot is required to have its own camera and computer vision processing onboard.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="lidar">LiDAR</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A LiDAR sensor can create a point cloud of a robot’s surroundings, and SLAM (Simultaneous Localization And Mapping) algorithms can automatically map an area and determine the robot’s position relative to other objects. However, LiDAR sensors are large and expensive, they must be mounted with a clear view of the robot’s surroundings, and the robot must have enough processing power to run these algorithms. Additionally, this system will not work as well in an open environment, where there won’t be any objects for the LiDAR to detect, and it might have difficulty determining the difference between stationary objects (such as walls) and moving objects (such as other robots in the area).</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="radio-trilateration">Radio trilateration</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">If a robot is able to determine the relative signal strength of multiple different radios (such as WiFi access points or Bluetooth beacons), and it knows the positions of these radios, it can estimate its position relative to them by assuming signal strength correlates with distance. Because there are many other variables that affect signal strength, such as objects between the robot and radio, this method is not very reliable. Also, robots that use wireless communication will need to make sure their transmissions do not affect their ability to determine their location. This problem is worse if multiple robots are present in an area.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="new-system">New System</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In the past, I have more or less given up on systems which do not accumulate error and instead programmed the robot to navigate using an open-loop system and then drive into a wall (of known position) every so often. As this solution doesn’t work well outside of a clearly-defined environment, I wanted to see if I could come up with a better solution for localization.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I set the following criteria for such a system:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Work well in scenarios where multiple robots are present</li>
<li class="my-2 pl-2">Not require complex processing on each robot</li>
<li class="my-2 pl-2">Be simple to set up in a given area (not requiring calibration)</li>
<li class="my-2 pl-2">Be relatively inexpensive and use commonly available parts</li>
<li class="my-2 pl-2">Not accumulate error over time</li>
</ul></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to primarily target a scenario with multiple tracked objects in a single area. Putting the heavy processing outside of the robot allows for more inexpensive robots powered by basic microcontrollers. Additionally, this allows passive objects in the scene to be tracked, allowing robots to interact with them.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I decided to try implementing a solution that worked outside the plane that the robots would be in, so that it would not be affected by adding more robots or objects into the area. Cameras are common and relatively cheap, so using a camera mounted on a tripod and pointed down at robots seemed like the best approach.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="1-image-scanning">1. Image Scanning</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="722" height="280" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-1.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fimage-scanning-1.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-1.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The system starts with a picture of the scene (left image). I’ve placed a computer vision marker in the scene along with a bunch of random objects.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Markers have a blue border around them to distinguish them from the environment. I used painters’ tape for this, as it has a consistent hue in a variety of lighting conditions. The system selects all the pixels in the image with this hue (and ample lightness and saturation).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This mask is shown in the right image. The marker is plainly visible here, and almost all of the other objects in the scene have been filtered out. The pencil and textbook happen to have the same hue, but they will be filtered out later.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="704" height="272" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-2.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fimage-scanning-2.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-2.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The first step in handling the masked areas is to determine their boundary. OpenCV has a convenient function for tracing the border of each region of interest.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="713" height="280" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-3.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fimage-scanning-3.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fimage-scanning-3.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Now, we have a series of points tracing the boundary of the marker. This isn’t a perfect quadrilateral for a variety of reasons (the camera introduces noise to the image, the lens introduces distortion, the marker isn’t completely square, the image resolution is low so the image is a bit blurry, etc). To approximate this boundary to four points, with one at each corner, we can use the Douglas-Peucker algorithm (conveniently also implemented by OpenCV).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">We can also do some filtering here to discard areas that aren’t markers. For instance, areas that are very elongated, aren’t quadrilaterals, or are very small probably aren’t valid markers. This leaves us with the marker in the image, surrounded by a red bounding box.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="2-transformation-calculation">2. Transformation Calculation</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The next step is to figure out what type of marker this is, and what its orientation is. Because there might be multiple markers in a scene, the black and white squares on the inside of the blue border give information on what type of marker it is.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using the bounding quadrilateral of the marker, we can calculate where we expect the squares to be. To do this, we need to calculate the transformation between the basis of the picture (i.e., with the origin at the top-left pixel, x-axis as the top row of pixels, and y-axis as the left row of pixels) and the basis of the scene. For reading the squares, it’s convenient to set the origin of the scene at the center of the marker, choose x and y axes arbitrarily, and set the length of each square to 1 unit.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="622" height="470" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-1.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-1.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-1.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It’s apparent that the sides of the marker aren’t parallel lines in the image, even though they are parallel in real life. Because the camera is a perspective camera, we need to use a perspective transformation (in which parallelism isn’t preserved). In order to calculate this type of transformation, we need to know the coordinates of four points in each basis. Conveniently, our quadrilateral bounding the marker has four points which we know the image coordinates of, and we know the real-life position of the marker’s corners relative to its center as well.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In order to compute a perspective transformation, we need a new coordinate system. Consider a point which appears on the horizon: it would be mapped an infinitely far distance away. Representing this isn’t possible with only X and Y coordinates, so we need a third coordinate.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="614" height="648" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-2.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-2.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Ftransformation-calculation-2.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The solution is a system known as homogeneous coordinates. To map our existing cartesian coordinates to homogeneous ones, we just include a 1 as the third coordinate. To map them back to cartesian coordinates, divide the X and Y coordinates by the third coordinate. This solves our horizon-point problem: Points with a third coordinate of 0 will have no defined equivalent cartesian coordinate.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">(While the system is operating normally, points on the horizon line shouldn’t ever actually be visible. Despite this, we still need to consider it in order to define the perspective transformation.)</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Using our new homogenous coordinates, we can calculate a transformation matrix which transforms coordinates in the picture to coordinates in real life (and invert that matrix to go the other way).</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="3-marker-identification">3. Marker Identification</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fmarker-identification-1.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fmarker-identification-1.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fmarker-identification-1.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because we know the real-life coordinates of the corners of each square, we can use the transformation matrix to map them to coordinates of pixels in the picture. The yellow grid in the image shows where the squares are expected to be.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At least one square is guaranteed to be each color. In order to compensate for varying lighting conditions, the system identifies the darkest and lightest squares, and then compares the lightness of the other squares to determine if they are colored black or white. On the left of the image, the grey square is the average lightness of the darkest square on the marker, and the white square is the average lightness of the lightest square on the marker.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">No pattern of squares is rotationally symmetrical, so the orientation of the pattern can be used to determine the orientation of the marker. The system can now draw the position and orientation of the marker over the camera feed.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="640" height="480" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fmarker-identification-2.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fmarker-identification-2.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fmarker-identification-2.png&amp;w=1920&amp;q=75"/></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="4-global-reference">4. Global Reference</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="737" height="289" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fglobal-reference-1.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fglobal-reference-1.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fglobal-reference-1.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I’ve added a few new markers into the scene. These markers have a different code on them: one black square and three white squares. I’ve also placed down a ruler for scale.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The system will distinguish between the original markers and the new markers. It plots each of the new markers on a grid, as well, relative to the original marker. You can see from the ruler that the furthest-right marker is about 12” away horizontally and 0” away vertically from the original marker, and the system plots its position as (12, 0).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The different square codes signify the different types of marker present. The original marker, with its 3-black, 1-white code, is the global reference - its position and size are used to plot the positions of the other markers. This means that the size of the global reference matters, but the size of the other markers does not (and you can see each marker in the image is a different size).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Mathematically, the hard work is already done: we can use the transformation matrices for the reference marker that have already been calculated. In a previous step, the system used the transformation matrix on the corners of each square, mapping each point’s coordinates in the scene to its coordinates in the picture. Similarly, we can use the reference’s inverse transformation matrix to map each additional marker’s coordinates in the picture to its coordinates in the scene.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The system plots an overhead-view grid of each visible marker. Note that the orientation and position of each marker is visible. Additionally, the size of each marker is known: smaller markers have a shorter arrow.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="500" height="500" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fglobal-reference-2.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fglobal-reference-2.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fglobal-reference-2.png&amp;w=1080&amp;q=75"/></div>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="5-post-processing-and-api">5. Post-Processing and API</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="500" height="500" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fpost-processing-1.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fpost-processing-1.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fpost-processing-1.png&amp;w=1080&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The raw plot can be kind of jittery because of image noise, low camera resolution, etc. The system will average the position of each marker over time to get a more stable and accurate plot. This smooth plot is also available using a REST API for other devices to use.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In order to do this, however, the system needs to keep track of each marker. In each new frame, when markers are found, it needs to determine if each marker is a new marker introduced into the scene or an existing marker being moved. To help keep track of markers across frames, letters are assigned. This also helps API clients keep track of markers.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="additional-feature-labels">Additional Feature: Labels</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="738" height="294" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Flabels-1.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Flabels-1.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Flabels-1.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">There is another type of marker with 2 black squares. These are labels, and they contain additional squares below the marker to identify it. These squares are read the same way as the squares inside the marker. As shown in the readout, this label has the number “1”. Labels show up with their number on both the raw plot and the smooth plot, and are identified by their number through the API.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="664" height="344" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Flabels-2.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Flabels-2.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Flabels-2.png&amp;w=1920&amp;q=75"/></div>
 
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="705" height="283" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Flabels-3.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Flabels-3.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Flabels-3.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I made a sheet of all possible labels. It’s difficult to see each one on the camera view, but the two plots show each label from 0 to 15.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="681" height="355" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Flabels-4.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Flabels-4.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Flabels-4.png&amp;w=1920&amp;q=75"/></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This project proved that a system using an overhead camera and computer vision markers can be a reliable method of localization for small robots. However, there are definitely issues with the approach that I did not anticipate.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="unreliable-marker-corner-detection">Unreliable Marker Corner Detection</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In this system, the corners of each marker were used to create the transformation matrices. Because of this, any issues in the detection of these corners will distort the resulting transformation and affect the calculated position of all other objects in the scene. This means that slightly misshapen markers, backgrounds with a similar hue to the markers, and other small issues can cause extremely inaccurate results.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This could be mitigated by using points within the marker to calculate this transformation, but overall, extrapolating one small reference marker to cover a large area will magnify any errors caused by lens distortion, image blur, etc. and make the entire system much less precise.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In order to increase the precision of the system, multiple reference markers should be used. However, this raises questions: How will the system know the distance between each marker? Will users be required to precisely set up multiple markers at exact distances from each other? In any case, this would make the system more difficult to deploy and use.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="unreliable-marker-filtering-from-background-objects">Unreliable Marker Filtering from Background Objects</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because the system detects markers based on color, any other blue objects in the scene may cause markers to be detected which aren’t actually markers. Additionally, color detection is unreliable because changes in lighting type and strength can influence how colors appear.</p>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="726" height="403" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fqr_pattern.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fqr_pattern.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fqr_pattern.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">An excerpt from the QR code specification describing how the &quot;Finder Pattern&quot; can be recognized.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A common method for detecting other sorts of computer vision markers is to look for some pattern in the lightness of the code. For instance, QR code scanners look for the 1-black, 1-white, 3-black, 1-white, 1-black pattern in the corners of each code. Designing a computer vision marker which can be identified using this technique would significantly reduce the error in this portion.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">However, using a method such as this one would require more processing power, making the system slower and less capable of running on low-end hardware like the Raspberry Pi. It might also reduce the camera resolution that the system can run at.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="poor-coverage-area">Poor Coverage Area</h3>
<div class="contents break-inside-avoid print:block"><img alt="" loading="lazy" width="736" height="628" decoding="async" data-nimg="1" class="max-w-[min(48rem,100%)] print:max-w-sm" style="color:transparent;max-height:40rem;margin:0 auto;object-fit:contain" srcSet="/_next/image?url=%2Fimages%2Flps%2Fcoverage_area.png&amp;w=750&amp;q=75 1x, /_next/image?url=%2Fimages%2Flps%2Fcoverage_area.png&amp;w=1920&amp;q=75 2x" src="/_next/image?url=%2Fimages%2Flps%2Fcoverage_area.png&amp;w=1920&amp;q=75"/></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">A diagram of the effective coverage area of my camera-based LPS system.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because of the camera’s limited resolution and field-of-view, the usable area of the system is weirdly shaped. This limits the effectiveness of the system, as most areas are not this weird shape. Multiple cameras could be used to increase the effective area, but this complicates the system and increases the cost significantly.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="closing-thoughts">Closing Thoughts</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Despite these issues, the system does accomplish the goals I set out at the beginning of the project. However, because of the system’s mediocre accuracy and reliability, I do not anticipate using it for anything in the future in its current state, although I do think this approach to localization is potentially useful (albeit with a lot more refinement).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I am curious as to how much better this sort of system would work if some of the aforementioned solutions were implemented. A system using four separate reference markers instead of one, QR-style finder pattern codes to help objects be more accurately recognized, and perhaps multiple cameras positioned on opposing sides of the area would likely have a much more useful accuracy. Although such a system would be more difficult and expensive to set up and operate, it would still be a practical and economical solution for providing localization capabilities to one or more robots in an enclosed area.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="faq-fully-anticipated-questions">FAQ (Fully Anticipated Questions)</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>What language was this written in?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I built this using Python, with OpenCV handling some of the heavy lifting for computer vision. I chose Python because I was familiar with it, but it would definitely be possible to implement a similar system in JavaScript for a web version.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why invent your own computer vision marker instead of using some existing standard?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tried to use QR codes, which would’ve saved me from doing most of the computer vision work and would’ve allowed me to focus on the linear algebra side of things, but the camera resolution was too low for that to work well. QR scanner libraries are built to recognize one or two codes positioned close to the camera, not many small codes positioned far away.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In hindsight, I’m kind of glad I had this problem, because working with computer vision was honestly pretty fun. I hadn’t really done anything in this field before.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why is the camera resolution so low?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I ran this at 480p because OpenCV isn’t able to grab 1080p video from my camera any faster than 5 fps. (Which isn’t really a problem on its own, as things aren’t generally moving that fast, but my camera has rolling shutter, which warped any moving markers on the screen into an unrecognizable state.) The processing done by my software doesn’t really take that much time, so it should be possible to run this sort of system at much higher resolutions.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why does the hue filter still capture non-blue things (e.g. the purple pencil and textbook)?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My camera doesn’t let me turn off auto white balance and automatic exposure compensation, so these parameters were frequently changing while I was trying to work on my system. Because of this, the hue range (and lightness/saturation ranges) I chose had to work for all sorts of white balance and exposure settings.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why not just buy or borrow a better camera?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The whole point of this project was to solve a difficult problem - precise localization - using tools I already owned. That means working around the inherent limitations of the hardware. I also wanted to try to make the system as cheap to deploy as possible, so I used my cheap-ish and commonly available webcam.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why blue painters’ tape for the border?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I tried using red, but my skin kept making it past the filter and screwing up the marker detection every time I reached into the scene. I also had a lot of problems with marker on paper being surprisingly glossy: portions of the border were appearing white on the camera instead of blue because of reflected light.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why only 16 possible labels?</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">At first, I let labels have an arbitrary number of squares. I found that the further squares were from the marker border, however, the less accurate the calculated position (yellow grid) would be, and these multi-line labels had a lot of scanning errors as a result.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><strong>Why not use colored squares to determine the marker type? This would let you define many more types of marker.</strong></p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This works fine for two colors, but trying to distinguish between any more becomes difficult, especially in a variety of lighting and white-balance scenarios. I tried using red-green-blue, but there were too many errors for it to work well.</p></div>]]></description>
            <link>https://breq.dev/projects/lps</link>
            <guid isPermaLink="false">/projects/lps</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[math]]></category>
            <pubDate>Tue, 01 Sep 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[McStatus.js]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">McStatus.js is a JavaScript library and API backend to embed a Minecraft server status readout.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to be able to check who was online on my Minecraft server without having to join. I also wanted a project where I could improve my understanding of JavaScript, the DOM, and web technologies in general.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-details">Technical Details</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg "><code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">mcstatus.js</code> loads a div complete with Minecraft server information into the DOM wherever it finds a <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">&lt;div class=&quot;mc-status&quot;&gt;</code>, using the <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">data-mc-server</code> attr to set the server IP. The status protocol for Minecraft servers uses raw TCP sockets, so a pure-JS server query-er isn&#x27;t possible. There are a lot of existing Minecraft server status tools, like <a href="https://api.mcsrvstat.us/" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">mcsrvstat.us</a>, but they don&#x27;t have a CORS header set, so they couldn&#x27;t be used from JavaScript. So, I implemented my own at <code class="-mx-px break-words rounded-sm bg-gray-200 p-0.5 font-mono dark:bg-gray-800">https://mcstatus.breq.dev/</code> with the bare minimum API for this project to work. I&#x27;m using Dinnerbone&#x27;s <a href="https://github.com/Dinnerbone/mcstatus" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">Server Pinger</a> under the hood for this.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It pretty much does everything I wanted it to do, and working on the project definitely gave me a better understanding of how to style websites using CSS. I didn&#x27;t really end up using the CSS/JS part for anything, but I did use the server component for <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/breqbot">Breqbot</a>&#x27;s Minecraft server functionality.</p></div>]]></description>
            <link>https://breq.dev/projects/mcstatus</link>
            <guid isPermaLink="false">/projects/mcstatus</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[flask]]></category>
            <pubDate>Sun, 16 Aug 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Vibrance]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/uvB-t6f3MoE/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/uvB-t6f3MoE/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Music in the video is made by my sibling, <a href="https://www.maxmichaelmusic.com/">Max Michael</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Vibrance is a tool to use a large number of computers or smartphones as a single output to create lighting effects. By setting the color on each screen in unison, colorful and interesting visual effects can be created. Vibrance also handles generating these effects based on some input, such as a Digital Audio Workstation, keyboard input, or custom-made lighting controller.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I remember seeing a video of a Coldplay concert in which every audience member had an LED wristband which would light up in sync with the music. It seemed like a cool way to involve the audience in a concert.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I wanted to make a light show like the Coldplay concert that would cost almost nothing. No big screen, no manufacturing hundreds of bracelets, just everyone&#x27;s personal devices and some controller software.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The Vibrance system has three main parts: the <em>controller</em>, the <em>relay</em>, and the <em>clients</em>. Working backwards from the clients:</p>
<img class="mx-auto" src="/images/diagrams/vibrance_simple.svg" alt="" width="521" height="281"/>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="clients">Clients</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The client code is a JavaScript app that runs on audience members&#x27; phones or computers. It receives messages from the relay and changes the color displayed on the user&#x27;s screen accordingly. These messages are JSON objects, which allows for a variety of extensions to be added. For instance, it is possible to direct a client to display certain text on its screen (song lyrics, a welcome message, etc). Because this code runs in JavaScript, and the messages require very low latency, I chose the WebSocket protocol.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="relay">Relay</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Most of the time, both the client devices and the controller will be connected to a public WiFi network or a cell network, and thus be hidden behind NAT. So, an intermediate server with a public IP address is required to facilitate the connection. The Relay fills this role.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Because it would be impractical for the controller to calculate separate colors for hundreds of clients, the clients are divided into zones. Client devices will indicate their zone to the relay (typically after prompting the user to choose which part of the room they are in). Messages sent to the relay by the controller are marked with their destination zone.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Upon receiving a message from the controller, the relay forwards it along to each device in its intended zone. In the background, the relay manages newly-connected clients, ensures connections are kept alive, and removes inactive clients.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="controller">Controller</h3>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The controller is the most complicated part of the pipeline. It is responsible for handling input from some device (through a <em>driver</em>), determining the color messages to send to the relay (using an <em>interface</em> and <em>script</em>), and sending these messages to the relay.</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="driver">Driver</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">The role of the Driver is to read input from some source and return it in a common format. Drivers can use a variety of inputs, such as MIDI ports, computer keyboards, and serial ports (e.g. for use with Arduino).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Virtual MIDI ports in particular are quite useful for reading input from a Digital Audio Workstation such as Ableton Live. By placing MIDI notes at certain points in the song, lighting effects can automatically be triggered.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Users can implement their own Drivers to use different controller devices (joysticks, Wii remotes, devices over the network, etc.)</p>
<h4 class="group -mb-2 mt-8 text-center font-display text-2xl underline focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="interface-and-scripts">Interface and Scripts</h4>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Users supply Scripts (written in Python) that describe how to translate inputs from Drivers into messages to send to the Relay. These scripts use an Interface to manage the drivers and relay connection. The Interface allows Vibrance to recover gracefully from a temporary connection failure, and it allows users to change the connected Driver and Relay at any time.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">For example, here is an example script that flashes zones 1 and 2, with a 1 second delay in between, whenever MIDI note 60 is received:</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#ff218c">@api</span><span class="" style="color:#ff218c">.</span><span class="" style="color:#ff218c">on</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;midi&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;note_on_60&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">def</span><span class=""> </span><span class="" style="color:#ff218c;font-style:italic">animation</span><span class="" style="color:#ff218c">(</span><span class="">event</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">:</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    api</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;FFF&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    api</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">4</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;000&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    api</span><span class="" style="color:#ff218c">.</span><span class="">wait</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    api</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">2</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;FFF&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">    api</span><span class="" style="color:#ff218c">.</span><span class="">color</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">3</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">4</span><span class="" style="color:#ff218c">)</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;000&quot;</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">A variety of example scripts are provided by Vibrance.</p>
<h3 class="group mb-4 mt-8 text-center font-display text-3xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="block-diagram">Block Diagram</h3>
<img class="mx-auto" src="/images/diagrams/vibrance.svg" alt="" width="891" height="801"/>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I don&#x27;t think I&#x27;ll have an opportunity to test Vibrance in the real world any time soon, because of the COVID-19 pandemic. However, I have tested it with multiple devices and with hundreds of simulated clients at my house.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I started this project a few weeks (!) before the stay-at-home orders started, and I had initially planned on trying it at the next opportunity my sibling had to perform their music. As an alternative, in the video I filmed demonstrating Vibrance, I emulated their Ableton Live setup as closely as possible.</p></div>]]></description>
            <link>https://breq.dev/projects/vibrance</link>
            <guid isPermaLink="false">/projects/vibrance</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[music]]></category>
            <pubDate>Mon, 15 Jun 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Red Storm Robotics]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/EVbv0NNOtKE/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/EVbv0NNOtKE/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Here&#x27;s a video of teams 4393X and 4393Z vs. 344R and 344X. At the time, I was on team 344R. 4393X was made up of the people I worked with back at MSSM.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">When I returned to Scarborough, I worked with the engineering teacher to start a VRC team. I helped with searching for and applying for grants, ordering parts, and building out a workspace. I then took on a leadership role in Team 344X where I helped show my teammates how to design solutions with VEX parts, how to build efficiently, how to program the robots, and (above all) how to think critically about a challenge.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In January, the team raised enough money from grants to spin off a second team, 344R. Several other students and I built a second robot (effectively starting halfway through the competition season) and competed under that number, eventually qualifying for the state championship and the world championship (both of which were canceled due to COVID-19).</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">In summer 2020, I decided to give back to the VRC community by contributing code to OkapiLib, a library used by hundreds of VEX teams for advanced functions such as motion profiling, PID control, and asynchronous movement. Notably, the 2019 World Skills Champions used OkapiLib for path generation in their Programming Skills routine. The library has almost ten thousand downloads on GitHub.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">My <a href="https://github.com/OkapiLib/OkapiLib/pull/445" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">pull request</a> added support for trigonometry, rounding, exponentiation, and other math functions to OkapiLib&#x27;s units API. Code that uses this API is checked at compile-time for common units errors, eliminating most mistakes caused when a programmer enters a formula incorrectly. My addition of math functions allows users to write formulas that use more than the basic four functions (add, subtract, multiply, divide) without sacrificing this compile-time units checking.</p></div>]]></description>
            <link>https://breq.dev/projects/344</link>
            <guid isPermaLink="false">/projects/344</guid>
            <category><![CDATA[robotics]]></category>
            <category><![CDATA[c++]]></category>
            <pubDate>Sat, 13 Jun 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[MSSM Penguins Robotics]]></title>
            <description><![CDATA[<div class="e-content font-body"><div class="my-4 aspect-video overflow-hidden rounded-2xl"><link rel="preload" href="https://i.ytimg.com/vi/nEkfTPRDO8k/hqdefault.jpg" as="image"/><article class="yt-lite " data-title="" style="background-image:url(https://i.ytimg.com/vi/nEkfTPRDO8k/hqdefault.jpg);--aspect-ratio:56.25%"><button type="button" class="lty-playbtn" aria-label="Watch "></button></article></div>
<p class="mx-auto mb-8 mt-2 max-w-xl text-balance text-center font-body">Here&#x27;s the autonomous routine I planned, developed, and tuned for the 2019 Maine State Championship on team 4393S. This autonomous routine got us the highest autonomous points ranking out of all 50 teams at the event.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was my first experience with a robotics competition. I did most of the building and all of the programming for my team. Our team won two local tournaments, was a finalist at the state championship, won the design award at the state championship, and competed at the world championship. This experience improved my mechanical skills, understanding of the design process, embedded programming skills, and teamwork abilities.</p>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">Here are a few of the things I&#x27;m most proud of from my work on this team:</p>
<div class="mx-auto max-w-prose text-lg"><ul class="ml-8 list-disc">
<li class="my-2 pl-2">Automatic aim using the VEX Vision Sensor to recognize flags</li>
<li class="my-2 pl-2">Buttons to automatically shoot multiple flags in quick succession using state machine logic, including sensors to track the position of balls, automatic reloading logic, and angle presets</li>
<li class="my-2 pl-2">Movement in an arc for saving time during the autonomous routine</li>
</ul></div></div>]]></description>
            <link>https://breq.dev/projects/4393</link>
            <guid isPermaLink="false">/projects/4393</guid>
            <category><![CDATA[robotics]]></category>
            <category><![CDATA[c++]]></category>
            <pubDate>Sat, 13 Jun 2020 00:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Mindjacker]]></title>
            <description><![CDATA[<div class="e-content font-body"><h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="overview">Overview</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was a wrapper for <a href="https://github.com/Eelviny/nxt-python" class="hover:underline text-panblue-dark dark:text-panblue focus:bg-panyellow outline-none">nxt-python</a> I wrote while I was in middle school for projects like the <a class="text-panblue-dark outline-none hover:underline focus:bg-panyellow" href="/projects/r2d2">R2D2</a>.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="motivation">Motivation</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">I liked the featureset of nxt-python, but I wanted to make it more Pythonic and add some common features (like playing audio) that I often used in robots.</p>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="technical-description">Technical Description</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">It&#x27;s a wrapper API, there&#x27;s not much to describe.</p>
<div class="text-md mx-auto my-2 flex w-max min-w-[min(100%,42rem)] max-w-full flex-col overflow-x-auto rounded-2xl border-2 border-black bg-[#fff5fc] py-2 pl-4 pr-8 font-mono dark:bg-gray-800 print:text-sm"><pre class="print:text-wrap"><div class="" style="color:#404040"><span class="" style="color:#8B5CF6">import</span><span class=""> mindjacker</span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">brick </span><span class="" style="color:#8B5CF6">=</span><span class=""> mindjacker</span><span class="" style="color:#ff218c">.</span><span class="">Brick</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">brick</span><span class="" style="color:#ff218c">.</span><span class="">move</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#ff218c">[</span><span class="" style="color:#1bb3ff">&quot;b&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#1bb3ff">&quot;c&quot;</span><span class="" style="color:#ff218c">]</span><span class="" style="color:#ff218c">,</span><span class=""> </span><span class="" style="color:#8B5CF6">100</span><span class="" style="color:#ff218c">,</span><span class=""> rotations</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">5</span><span class="" style="color:#ff218c">,</span><span class=""> steer</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">50</span><span class="" style="color:#ff218c">,</span><span class=""> brake</span><span class="" style="color:#8B5CF6">=</span><span class="" style="color:#8B5CF6">False</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="">brick</span><span class="" style="color:#ff218c">.</span><span class="">playSound</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;sound.mp3&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">measurement </span><span class="" style="color:#8B5CF6">=</span><span class=""> brick</span><span class="" style="color:#ff218c">.</span><span class="">ultrasonic</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#8B5CF6">1</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class=""></span><span class="" style="color:#8B5CF6">print</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">f&quot;Ultrasonic sensor measures </span><span class="" style="color:#ff218c">{</span><span class="">measurement</span><span class="" style="color:#ff218c">}</span><span class="" style="color:#1bb3ff"> inches&quot;</span><span class="" style="color:#ff218c">)</span><span class=""></span></div><div class="" style="color:#404040"><span class="" style="display:inline-block">
</span></div><div class="" style="color:#404040"><span class="">brick</span><span class="" style="color:#ff218c">.</span><span class="">write</span><span class="" style="color:#ff218c">(</span><span class="" style="color:#1bb3ff">&quot;data.log&quot;</span><span class="" style="color:#ff218c">,</span><span class=""> measurement</span><span class="" style="color:#ff218c">)</span></div></pre></div>
<h2 class="group mx-auto mb-4 mt-8 max-w-4xl text-center font-display text-4xl focus-within:text-panblue-dark" style="scroll-margin-top:100px" id="results">Results</h2>
<p class="mx-auto my-4 max-w-prose font-body text-lg ">This was one of the first APIs I actually designed. It&#x27;s a pretty flawed design, but it was a learning experience. This was also one of the first times I wrote software to make it easier for me to write more software, and I decided to make it open-source.</p></div>]]></description>
            <link>https://breq.dev/projects/mindjacker</link>
            <guid isPermaLink="false">/projects/mindjacker</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[hardware]]></category>
            <pubDate>Sat, 13 Jun 2020 00:00:00 GMT</pubDate>
        </item>
    </channel>
</rss>