<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Life as a solarpunk hacker</title>
  
  <subtitle>Patterns of consciousness in a sea of matter</subtitle>
  <link href="http://blog.mikey.nz/atom.xml" rel="self"/>
  
  <link href="http://blog.mikey.nz/"/>
  <updated>2025-05-27T10:46:54.273Z</updated>
  <id>http://blog.mikey.nz/</id>
  
  <author>
    <name>Mikey (@ahdinosaur)</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>First look at Blinksy</title>
    <link href="http://blog.mikey.nz/first-look-at-blinksy/"/>
    <id>http://blog.mikey.nz/first-look-at-blinksy/</id>
    <published>2025-05-27T08:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>Oops I went down a rabbit hole and discovered this: <a href="https://github.com/ahdinosaur/blinksy">Blinksy</a> 🟥🟩🟦</p><blockquote><p>A <strong>Rust</strong> <em>no-std</em> <em>no-alloc</em> LED control library for spatial layouts</p></blockquote><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/1085561394?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy: 2D APA102 Grid with Noise Pattern"></div><h2 id="What’s-in-this-post">What’s in this post?<a class="header-anchor" href="#What’s-in-this-post">§</a></h2><ul><li><a href="#Backstory">Backstory</a></li><li><a href="#Announcing-Blinksy">Announcing: Blinksy</a><ul><li><a href="#Examples">Examples</a><ul><li><a href="#Desktop-Simulation-2D-Grid-with-Noise-Pattern">Desktop Simulation: 2D Grid with Noise Pattern</a></li><li><a href="#Embedded-2D-APA102-Grid-with-Noise-Pattern">Embedded: 2D APA102 Grid with Noise Pattern</a></li><li><a href="#Embedded-1D-WS2812-Strip-with-Rainbow-Pattern">Embedded: 1D WS2812 Strip with Rainbow Pattern</a></li></ul></li></ul></li><li><a href="#How-Blinksy-works">How Blinksy works</a><ul><li><a href="#Define-your-LED-layout">Define your LED layout</a><ul><li><a href="#1D-layouts">1D layouts</a></li><li><a href="#2D-layouts">2D layouts</a></li></ul></li><li><a href="#Create-your-visual-pattern">Create your visual pattern</a></li><li><a href="#Setup-your-LED-driver">Setup your LED driver</a><ul><li><a href="#What-colors-do-an-LED-understand">What colors do an LED understand?</a></li><li><a href="#What-protocols-do-an-LED-understand">What protocols do an LED understand?</a></li><li><a href="#What-LEDs-can-we-talk-to">What LEDs can we talk to?</a></li></ul></li></ul></li><li><a href="#Get-started">Get started</a><ul><li><a href="#Put-everything-together">Put everything together</a></li><li><a href="#Get-running-on-a-microcontroller">Get running on a microcontroller</a></li><li><a href="#Add-some-LEDs">Add some LEDs</a></li><li><a href="#Hello-LEDs">Hello LEDs</a></li><li><a href="#Simulate-on-your-desktop">Simulate on your desktop</a></li><li><a href="#Quickstart-a-project">Quickstart a project</a></li></ul></li><li><a href="#Thanks">Thanks</a></li></ul><h2 id="Backstory">Backstory<a class="header-anchor" href="#Backstory">§</a></h2><p>I wanted to make a LED control library that could do the following:</p><ul><li>Like <a href="https://fastled.io/">FastLED</a>, support all the most common LED pixel chipsets such as WS2812, APA102, and more.</li><li>Like <a href="https://kno.wled.ge">WLED</a>, have a library of beautiful visual patterns.</li><li>Unlike anything before, support not just strips and grids, but any 1D, 2D, or even 3D spatial layout.</li><li>By using Rust, have an modern and delightful developer experience.</li></ul><p>I had some previous learnings with LED pixels:</p><ul><li><a href="/pixels-for-the-pixel-god/">PIXELS FOR THE PIXEL GOD</a></li><li><a href="/a-burn-dance/">A Burn Dance</a></li><li><a href="/polyledra-v1-led-tetrahedron/">Polyledra V1: LED Tetrahedron</a></li><li><a href="/polyledra-v2-led-tensegrity/">Polyledra V2: LED Tensegrity</a></li></ul><p>And learnings with <a href="/how-to-dance-with-embedded-rust-generics/">advanced generics for no-std no-alloc embedded Rust</a>.</p><h2 id="Announcing-Blinksy">Announcing: Blinksy<a class="header-anchor" href="#Announcing-Blinksy">§</a></h2><p>Blinksy is a new LED control library for 1D, 2D, and soon 3D spatial layouts*.</p><p><em>* 3D layouts are coming soon, because I want an LED cube like <a href="https://makerworld.com/en/models/1085530-16x16-ws2812-wled-cube">this</a> with native 3D animations!</em></p><h3 id="Examples">Examples<a class="header-anchor" href="#Examples">§</a></h3><h4 id="Desktop-Simulation-2D-Grid-with-Noise-Pattern">Desktop Simulation: 2D Grid with Noise Pattern<a class="header-anchor" href="#Desktop-Simulation-2D-Grid-with-Noise-Pattern">§</a></h4><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/1085562226?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Desktop: 2D Grid with Noise Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::&#123;Shape2d, Vec2&#125;,</span><br><span class="line">    layout2d,</span><br><span class="line">    patterns::noise::&#123;noise_fns, Noise2d, NoiseParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> blinksy_desktop::&#123;</span><br><span class="line">    driver::&#123;Desktop, DesktopError&#125;,</span><br><span class="line">    time::elapsed_in_ms,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> std::&#123;thread::sleep, time::Duration&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    layout2d!(</span><br><span class="line">        Layout,</span><br><span class="line">        [Shape2d::Grid &#123;</span><br><span class="line">            start: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            horizontal_end: Vec2::<span class="title function_ invoke__">new</span>(<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            vertical_end: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., <span class="number">1</span>.),</span><br><span class="line">            horizontal_pixel_count: <span class="number">16</span>,</span><br><span class="line">            vertical_pixel_count: <span class="number">16</span>,</span><br><span class="line">            serpentine: <span class="literal">true</span>,</span><br><span class="line">        &#125;]</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_2d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Noise2d&lt;noise_fns::Perlin&gt;&gt;(NoiseParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(Desktop::new_2d::&lt;Layout&gt;())</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Err</span>(DesktopError::WindowClosed) = control.<span class="title function_ invoke__">tick</span>(<span class="title function_ invoke__">elapsed_in_ms</span>()) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="title function_ invoke__">sleep</span>(Duration::<span class="title function_ invoke__">from_millis</span>(<span class="number">16</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><h3 id="Embedded-2D-APA102-Grid-with-Noise-Pattern">Embedded: 2D APA102 Grid with Noise Pattern<a class="header-anchor" href="#Embedded-2D-APA102-Grid-with-Noise-Pattern">§</a></h3><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/1085561112?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Embedded: 2D APA102 Grid with Noise Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::&#123;Shape2d, Vec2&#125;,</span><br><span class="line">    layout2d,</span><br><span class="line">    patterns::noise::&#123;noise_fns, Noise2d, NoiseParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> gledopto::&#123;apa102, board, elapsed, main&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[main]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = board!();</span><br><span class="line"></span><br><span class="line">    layout2d!(</span><br><span class="line">        Layout,</span><br><span class="line">        [Shape2d::Grid &#123;</span><br><span class="line">            start: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            horizontal_end: Vec2::<span class="title function_ invoke__">new</span>(<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            vertical_end: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., <span class="number">1</span>.),</span><br><span class="line">            horizontal_pixel_count: <span class="number">16</span>,</span><br><span class="line">            vertical_pixel_count: <span class="number">16</span>,</span><br><span class="line">            serpentine: <span class="literal">true</span>,</span><br><span class="line">        &#125;]</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_2d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Noise2d&lt;noise_fns::Perlin&gt;&gt;(NoiseParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(apa102!(p))</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    control.<span class="title function_ invoke__">set_brightness</span>(<span class="number">0.1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">elapsed_in_ms</span> = <span class="title function_ invoke__">elapsed</span>().<span class="title function_ invoke__">as_millis</span>();</span><br><span class="line">        control.<span class="title function_ invoke__">tick</span>(elapsed_in_ms).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><h4 id="Embedded-1D-WS2812-Strip-with-Rainbow-Pattern">Embedded: 1D WS2812 Strip with Rainbow Pattern<a class="header-anchor" href="#Embedded-1D-WS2812-Strip-with-Rainbow-Pattern">§</a></h4><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/1085561502?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Embedded: 1D WS2812 Strip with Rainbow Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::Layout1d,</span><br><span class="line">    layout1d,</span><br><span class="line">    patterns::rainbow::&#123;Rainbow, RainbowParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> gledopto::&#123;board, elapsed, main, ws2812&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[main]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = board!();</span><br><span class="line"></span><br><span class="line">    layout1d!(Layout, <span class="number">60</span> * <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_1d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Rainbow&gt;(RainbowParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(ws2812!(p, Layout::PIXEL_COUNT))</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    control.<span class="title function_ invoke__">set_brightness</span>(<span class="number">0.2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">elapsed_in_ms</span> = <span class="title function_ invoke__">elapsed</span>().<span class="title function_ invoke__">as_millis</span>();</span><br><span class="line">        control.<span class="title function_ invoke__">tick</span>(elapsed_in_ms).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><h2 id="How-Blinksy-works">How Blinksy works<a class="header-anchor" href="#How-Blinksy-works">§</a></h2><ul><li>Define your LED <a href="https://docs.rs/blinksy/0.4/blinksy/layout/index.html"><code>layout</code></a> in 1D, 2D, or soon 3D space</li><li>Create your visual <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html"><code>pattern</code></a> (effect), or choose from our built-in <a href="https://docs.rs/blinksy/0.4/blinksy/patterns/index.html"><code>patterns</code></a> library<ul><li>The pattern will compute colors for each LED based on its position</li></ul></li><li>Setup a <a href="https://docs.rs/blinksy/0.4/blinksy/driver/index.html"><code>driver</code></a> to send each frame of colors to your LEDs, using our built-in <a href="https://docs.rs/blinksy/0.4/blinksy/drivers/index.html"><code>drivers</code></a> library.</li></ul><p>To best support embedded devices, this is possible without the Rust standard library (<em>no-std</em>) and without any memory allocations to the heap (<em>no-alloc</em>).</p><h3 id="Define-your-LED-layout">Define your LED layout<a class="header-anchor" href="#Define-your-LED-layout">§</a></h3><p>A <a href="https://docs.rs/blinksy/0.4/blinksy/layout/index.html">layout</a> defines the physical or logical positions of the LEDs in your setup, as arrangements in 1D, 2D, and 3D space.</p><p>To define a layout, we must define a struct that implement either the <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout1d.html"><code>Layout1d</code></a>, <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout2d.html"><code>Layout2d</code></a>, or soon <code>Layout3d</code> traits. To make this easy, we use either the <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout1d.html"><code>layout1d</code></a>, <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout2d.html"><code>layout2d</code></a>, or soon <code>layout3d</code> macro, respectively. These traits provide a <code>PIXEL_COUNT</code> constant, which is the number of LEDs, and a <code>.points()</code> method, which maps each LED pixel into a 1D, 2D, or 3D space between -1.0 and 1.0.</p><p>(For any Rust beginners: A <a href="https://doc.rust-lang.org/book/ch05-01-defining-structs.html">struct</a> is a definition of a type of object, like a class in other languages. A <a href="https://doc.rust-lang.org/book/ch10-02-traits.html">trait</a> is a definition of an abstract behavior that an object might implement, like an interface in other languages. A <a href="https://doc.rust-lang.org/book/ch20-05-macros.html">macro</a> is code that generates code, like a function that you call during compilation to return code, rather than call during runtime to return a value.)</p><h4 id="1D-layouts">1D layouts<a class="header-anchor" href="#1D-layouts">§</a></h4><p>For a 1D layout, this is very simple, as a 1D shape only has a length.</p><p>Here is a layout for an LED strip with 60 pixels.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">layout1d!(Layout, <span class="number">60</span>);</span><br></pre></td></tr></table></figure><p>For our 1D space, the first LED pixel will be at -1.0 and the last LED pixel will be at 1.0.</p><div style="text-align: center">  <div class="image-wrapper"><img src="/first-look-at-blinksy/layout-1d-points.svg" alt="Layout 1d points" width="100%" / loading="lazy" /></div></div><h4 id="2D-layouts">2D layouts<a class="header-anchor" href="#2D-layouts">§</a></h4><p>For a 2D layout, you need to define your 2D shapes: points, lines, grids, arcs, etc.</p><p>For our 2D space, we can think of:</p><ul><li><code>(-1.0, -1.0)</code> as the bottom left</li><li><code>(1.0, -1.0)</code> as the bottom right</li><li><code>(-1.0, 1.0)</code> as the top left</li><li><code>(1.0, 1.0)</code> as the top right</li></ul><p>Here is a layout for a basic 16x16 LED grid panel to span our 2D space:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">layout2d!(</span><br><span class="line">    Layout,</span><br><span class="line">    [Shape2d::Grid &#123;</span><br><span class="line">        start: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">        horizontal_end: Vec2::<span class="title function_ invoke__">new</span>(<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">        vertical_end: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., <span class="number">1</span>.),</span><br><span class="line">        horizontal_pixel_count: <span class="number">16</span>,</span><br><span class="line">        vertical_pixel_count: <span class="number">16</span>,</span><br><span class="line">        serpentine: <span class="literal">true</span>,</span><br><span class="line">    &#125;]</span><br><span class="line">);</span><br></pre></td></tr></table></figure><div style="text-align: center">  <div class="image-wrapper"><img src="/first-look-at-blinksy/layout-2d-points.svg" alt="Layout 2d points" width="100%" / loading="lazy" /></div></div><h3 id="Create-your-visual-pattern">Create your visual pattern<a class="header-anchor" href="#Create-your-visual-pattern">§</a></h3><p>A <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html">pattern</a>, most similar to <a href="https://kno.wled.ge/features/effects/">a WLED effect</a>, generates colors for LEDs based on time and position.</p><p>We define this as a struct that implements the <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html"><code>Pattern</code></a> trait.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/// # Type Parameters</span></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="comment">/// * `Dim` - The dimension marker (Dim1d or Dim2d)</span></span><br><span class="line"><span class="comment">/// * `Layout` - The specific layout type</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">trait</span> <span class="title class_">Pattern</span>&lt;Dim, Layout&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    Layout: LayoutForDim&lt;Dim&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/// The configuration parameters type for this pattern.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Params</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// The color type produced by this pattern.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Color</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Creates a new pattern instance with the specified parameters.</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">new</span>(params: <span class="keyword">Self</span>::Params) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Generates colors for all LEDs in the layout at the given time.</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// # Arguments</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// * `time_in_ms` - The current time in milliseconds</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// # Returns</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// An iterator yielding one color per LED in the layout</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">tick</span>(&amp;<span class="keyword">self</span>, time_in_ms: <span class="type">u64</span>) <span class="punctuation">-&gt;</span> <span class="keyword">impl</span> <span class="title class_">Iterator</span>&lt;Item = <span class="keyword">Self</span>::Color&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>While there is one <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html"><code>Pattern</code></a> trait, it may be implemented for any dimension, using a <a href="https://docs.rs/blinksy/0.4/blinksy/dimension/index.html">dimension marker</a>: <a href="https://docs.rs/blinksy/0.4/blinksy/dimension/struct.Dim1d.html"><code>Dim1d</code></a>, <a href="https://docs.rs/blinksy/0.4/blinksy/dimension/struct.Dim2d.html"><code>Dim2d</code></a>, or soon <code>Dim3d</code>. The dimension marker will then constrain the <code>Layout</code> generic provided, to implement either <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout1d.html"><code>Layout1d</code></a>, <a href="https://docs.rs/blinksy/0.4/blinksy/layout/trait.Layout2d.html"><code>Layout2d</code></a>, or soon <code>Layout3d</code>, respectively.</p><p>On initialization, the pattern is given configuration parameters. On every update, the pattern is given the current time in milliseconds, and must return an iterator that provides a color for every LED in the layout.</p><p>The <a href="https://docs.rs/blinksy/0.4/blinksy/color/index.html">color types</a> in Blinksy are inspired by the <a href="https://docs.rs/palette/0.7/palette/"><code>palette</code></a> crate, where they implement <a href="https://docs.rs/blinksy/0.4/blinksy/color/trait.FromColor.html"><code>FromColor</code></a> and <a href="https://docs.rs/blinksy/0.4/blinksy/color/trait.IntoColor.html"><code>IntoColor</code></a>. Like FastLED we have <a href="https://docs.rs/blinksy/0.4/blinksy/color/struct.Hsv.html"><code>Hsv</code></a> (which uses <a href="https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors">FastLED’s rainbow hues</a>), or for a <a href="https://bottosson.github.io/posts/colorpicker/">more modern</a> color space we have <a href="https://docs.rs/blinksy/0.4/blinksy/color/struct.Okhsv.html"><code>Okhsv</code></a>.</p><p>To use a pattern, we can either choose from the <a href="https://docs.rs/blinksy/0.4/blinksy/patterns/index.html">built-in library</a> or create our own.</p><p>We have two visual <a href="https://docs.rs/blinksy/0.4/blinksy/patterns/index.html">patterns</a> to start, each implementing <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html"><code>Pattern</code></a> for 1D, 2D, and soon 3D.</p><ul><li><a href="https://docs.rs/blinksy/0.4/blinksy/patterns/rainbow/index.html">Rainbow</a>: A basic scrolling rainbow.</li><li><a href="https://docs.rs/blinksy/0.4/blinksy/patterns/noise/index.html">Noise</a>: A flow through random noise functions.</li></ul><p>Or feel free to make your own. Better yet, help contribute to our library!</p><h3 id="Setup-your-LED-driver">Setup your LED driver<a class="header-anchor" href="#Setup-your-LED-driver">§</a></h3><p>Now for the final step.</p><p>The driver is what tells the LED hardware how to be the colors you want.</p><p>To define a driver, we must implement the <a href="https://docs.rs/blinksy/0.4/blinksy/driver/index.html"><code>Driver</code></a> trait:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">trait</span> <span class="title class_">Driver</span> &#123;</span><br><span class="line">    <span class="comment">/// The error type that may be returned by the driver.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// The color type accepted by the driver.</span></span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Color</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// Writes a sequence of colors to the LED hardware.</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// # Arguments</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// * `pixels` - Iterator over colors</span></span><br><span class="line">    <span class="comment">/// * `brightness` - Global brightness scaling factor (0.0 to 1.0)</span></span><br><span class="line">    <span class="comment">/// * `correction` - Color correction factors</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// # Returns</span></span><br><span class="line">    <span class="comment">///</span></span><br><span class="line">    <span class="comment">/// Result indicating success or an error</span></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">write</span>&lt;I, C&gt;(</span><br><span class="line">        &amp;<span class="keyword">mut</span> <span class="keyword">self</span>,</span><br><span class="line">        pixels: I,</span><br><span class="line">        brightness: <span class="type">f32</span>,</span><br><span class="line">        correction: ColorCorrection,</span><br><span class="line">    ) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;</span><br><span class="line">    <span class="keyword">where</span></span><br><span class="line">        I: <span class="built_in">IntoIterator</span>&lt;Item = C&gt;,</span><br><span class="line">        <span class="keyword">Self</span>::Color: FromColor&lt;C&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The driver says what type of error it might return and what type of color it wants to receive.</p><p>When you write to a driver, you provide an iterator of colors (in your own color type), plus a global brightness and color correction.</p><h4 id="What-colors-do-an-LED-understand">What colors do an LED understand?<a class="header-anchor" href="#What-colors-do-an-LED-understand">§</a></h4><p>The LED driver will be given colors from the visual pattern, then convert them into a new color type more suitable to what the LED hardware understands.</p><p>LEDs are generally 3 smaller LEDs, red + green + blue, each controlled via <a href="https://en.wikipedia.org/wiki/Pulse-width_modulation#Duty_cycle">pulse-width modulation (PWM)</a>. If you tell an LED to be 100% bright, it will be on for 100% of the time (a 100% duty cycle). If you tell an LED to be 50% bright, it will be on for 50% of the time (a 50% duty cycle). And so on. Our eyes don’t notice the flicker on and off.</p><div style="text-align: center">  <div class="image-wrapper"><img    src="/first-look-at-blinksy/led-pwm.svg"    alt="LED PWM (Pulse-Width Modulation)"    style="max-width: 768px;"   loading="lazy" /></div></div><p>Therefore, we use <a href="https://docs.rs/blinksy/0.4/blinksy/color/struct.LinearSrgb.html"><code>LinearSrgb</code></a> when thinking about LEDs, since linear color values correspond to the <a href="https://en.wikipedia.org/wiki/Luminous_intensity">luminous intensity</a> of light, i.e. how many photons should be emitted. However, what we actually perceive in a linear change in photons is not linear. For our evolutionary survival, we are much more sensitive to changes in dim light than we are to changes in bright light. If you double the amount of photons, we don’t see double the brightness.</p><p>This mismatch between physics and perception is why the “RGB” you think you know is actually <a href="https://en.wikipedia.org/wiki/SRGB">gamma-encoded <code>sRGB</code></a>. sRGB allows us to think in terms of perception, where double the red value means double the perceived brightness of red. Then for LEDs, we convert the gamma-encoded sRGB to linear, to use as a gamma-corrected duty cycle.</p><div style="text-align: center">  <div class="image-wrapper"><img    src="/first-look-at-blinksy/gamma-correction.svg"    alt="Gamma Correction"    style="max-width: 512px;"   loading="lazy" /></div></div><p>By the way, if you start mixing RGB’s, make sure to do so in the linear space.</p><p>So anyways, why red, green, and blue? These correspond to the 3 light receptors in our eyes. What we perceive as color is some combination of these receptors being trigged. Our brain doesn’t know the difference between seeing the yellow wavelength as seeing a combination of the green and red wavelengths at the same time. We don’t see color as we might think. There’s no such wavelength for purple (not to be confused with violet), yet our brain makes up a color that we see.</p><a href="https://commons.wikimedia.org/w/index.php?curid=10514373">  <div style="text-align: center">    <div class="image-wrapper"><img      src="/first-look-at-blinksy/human-eye-cones.svg"      alt="Normalized responsivity spectra of human cone cells, S, M, and L types (SMJ data based on Stiles and Burch RGB color-matching, linear scale, weighted for equal energy)"      style="max-width: 512px; width: 100%;"     loading="lazy" /></div>  </div></a><p>There’s more. We say RGB, but what red, what green, what blue? To solve this, the sRGB color space defines an exact red, an exact green, an exact blue. But is the color in our color system the same as the color being output by our LEDs?</p><p>I could go on about colors, there’s more to say, more future work to be done in Blinksy, but that’s enough for now.</p><h4 id="What-protocols-do-an-LED-understand">What protocols do an LED understand?<a class="header-anchor" href="#What-protocols-do-an-LED-understand">§</a></h4><p>To make implementing <a href="https://docs.rs/blinksy/0.4/blinksy/driver/index.html"><code>Driver</code></a> easier for the various LED chipsets, we have generic support for the two main types of LED protocols:</p><h5 id="Clocked-LEDs-SPI">Clocked LEDs (<a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface">SPI</a>)<a class="header-anchor" href="#Clocked-LEDs-SPI">§</a></h5><p>A <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clocked/index.html"><code>clocked</code></a> protocol is based on <a href="https://en.wikipedia.org/wiki/Serial_Peripheral_Interface">SPI</a>, where chipsets have a data line and a clock line.</p><p>For every bit we want to send from the controller to the LEDs:</p><ul><li>First the controller sets the data line (MOSI) to HIGH for a 1 or LOW for a 0.</li><li>Then the controller “ticks” the clock line (SCLK), by going from LOW to HIGH.</li><li>On the rising edge of the clock line, the LED will read the data line.</li><li>Halfway through the clock cycle, the controller will reset the clock line to LOW.</li></ul><div style="text-align: center">  <div class="image-wrapper"><img    src="/first-look-at-blinksy/clocked-transmission.svg"    alt="Clocked byte example"   loading="lazy" /></div></div><p>To define a clocked LED chipset, you define the <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clocked/trait.ClockedLed.html"><code>ClockedLed</code></a> trait.</p><h5 id="Clockless-LEDs">Clockless LEDs<a class="header-anchor" href="#Clockless-LEDs">§</a></h5><p>A <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clockless/index.html"><code>clockless</code></a> protocol is based on specific timing periods, where chipsets have only a single data line.</p><p>Without a clock, the protocol depends on specific timings to represent each bit.</p><p>For example with <a href="https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf">WS2812B LEDs</a>, to represent a 0 bit the data line must be HIGH for 0.4 microseconds, then LOW for 0.85 microseconds. These timings must be accurate to within 150 nanoseconds. That’s tiny!</p><div style="text-align: center">  <div class="image-wrapper"><img    src="/first-look-at-blinksy/clockless-timing.svg"    alt="Clockless bit timing"    style="max-width: 360px"   loading="lazy" /></div></div><p>With these timings in mind, we can send some bits without a clock.</p><div style="text-align: center">  <div class="image-wrapper"><img    src="/first-look-at-blinksy/clockless-transmission.svg"    alt="Clockless byte example"   loading="lazy" /></div></div><p>To define a clockless LED chipset, you define the <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clockless/trait.ClocklessLed.html"><code>ClocklessLed</code></a> trait.</p><h5 id="Protocol-drivers">Protocol drivers<a class="header-anchor" href="#Protocol-drivers">§</a></h5><p>For each generic LED protocol type, we have specific protocol drivers for those types of LEDs:</p><ul><li>By bit-banging over GPIO pins, using a delay timer.</li><li>Or by using an SPI peripheral.</li></ul><p>For clockless protocols on ESP devices, we can also use the <a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html">RMT peripheral</a>.</p><p>(Note: I have yet to drive clockless LEDs using an SPI peripheral. For now I’m happy with my ESP32’s RMT peripheral. If you want this, maybe you can <a href="https://github.com/ahdinosaur/blinksy/issues/12">help</a>?)</p><h4 id="What-LEDs-can-we-talk-to">What LEDs can we talk to?<a class="header-anchor" href="#What-LEDs-can-we-talk-to">§</a></h4><p>At the moment, Blinksy supports:</p><ul><li><a href="https://docs.rs/blinksy/0.4/blinksy/drivers/apa102/index.html">APA102 LEDs</a>, aka DotStar<ul><li><a href="https://docs.rs/blinksy/0.4/blinksy/drivers/apa102/type.Apa102Delay.html">Apa102Delay</a></li><li><a href="https://docs.rs/blinksy/0.4/blinksy/drivers/apa102/type.Apa102Spi.html">Apa102Spi</a></li></ul></li><li><a href="https://docs.rs/blinksy/0.4/blinksy/drivers/ws2812/index.html">WS2812 LEDs</a>, aka NeoPixel<ul><li><a href="https://docs.rs/blinksy/0.4/blinksy/drivers/ws2812/type.Ws2812Delay.html">Ws2812Delay</a></li><li><a href="https://docs.rs/blinksy-esp/0.4/blinksy_esp/type.Ws2812Rmt.html">Ws2812Rmt</a></li></ul></li></ul><p>With the above protocol abstractions, adding a new LED chipset is as easy as implementing <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clocked/trait.ClockedLed.html">ClockedLed</a> or <a href="https://docs.rs/blinksy/0.4/blinksy/driver/clockless/trait.ClocklessLed.html">ClocklessLed</a>.</p><p>By the way, props to <a href="https://github.com/smart-leds-rs/smart-leds"><code>smart-leds</code></a> for paving the way on addressable LED drivers in Rust.</p><h2 id="Get-started">Get started<a class="header-anchor" href="#Get-started">§</a></h2><h3 id="Put-everything-together">Put everything together<a class="header-anchor" href="#Put-everything-together">§</a></h3><p>Okay, now that we’ve learned about <a href="https://docs.rs/blinksy/0.4/blinksy/layout/index.html">layout</a>, a <a href="https://docs.rs/blinksy/0.4/blinksy/pattern/index.html">pattern</a>, and <a href="https://docs.rs/blinksy/0.4/blinksy/driver/index.html">driver</a> – let’s put them together.</p><p>We use a <a href="https://docs.rs/blinksy/0.4/blinksy/control/struct.ControlBuilder.html"><code>ControlBuilder</code></a> to build a <a href="https://docs.rs/blinksy/0.4/blinksy/control/struct.Control.html"><code>Control</code></a>.</p><p>For 1D:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_1d</span>()</span><br><span class="line">    .with_layout::&lt; <span class="comment">/* layout type */</span> &gt;()</span><br><span class="line">    .with_pattern::&lt; <span class="comment">/* pattern type */</span> &gt;(<span class="comment">/* pattern params */</span>)</span><br><span class="line">    .<span class="title function_ invoke__">with_driver</span>(<span class="comment">/* driver */</span>)</span><br><span class="line">    .<span class="title function_ invoke__">build</span>();</span><br></pre></td></tr></table></figure><p>For 2D:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_2d</span>()</span><br><span class="line">    .with_layout::&lt; <span class="comment">/* layout type */</span> &gt;()</span><br><span class="line">    .with_pattern::&lt; <span class="comment">/* pattern type */</span> &gt;(<span class="comment">/* pattern params */</span>)</span><br><span class="line">    .<span class="title function_ invoke__">with_driver</span>(<span class="comment">/* driver */</span>)</span><br><span class="line">    .<span class="title function_ invoke__">build</span>();</span><br></pre></td></tr></table></figure><p><a href="https://docs.rs/blinksy/0.4/blinksy/control/struct.ControlBuilder.html"><code>ControlBuilder</code></a> means we don’t have to think about all generic types involved in a <a href="https://docs.rs/blinksy/0.4/blinksy/control/struct.Control.html"><code>Control</code></a>, we can add each part one at a time.</p><p>From here we can set a global brightness or color correction.</p><p>Then we run our main loop, calling <a href="https://docs.rs/blinksy/0.4/blinksy/control/struct.Control.html#method.tick"><code>.tick()</code></a> with the current time in milliseconds.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">loop</span> &#123;</span><br><span class="line">    control.<span class="title function_ invoke__">tick</span>(<span class="comment">/* current time in milliseconds */</span>).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Get-running-on-a-microcontroller">Get running on a microcontroller<a class="header-anchor" href="#Get-running-on-a-microcontroller">§</a></h3><p>While you can plug LEDs directly into microcontroller pins, I do recommend using an LED controller that does things properly.</p><p>I found a decent LED controller available on AliExpress: <a href="https://www.aliexpress.com/item/1005008707989546.html">Gledopto GL-C-016WL-D</a>.</p><p><a href="https://www.aliexpress.com/item/1005008707989546.html"><div class="image-wrapper"><img srcset="/first-look-at-blinksy/sm_gledopto-gl-c-016wl-d.jpg 480w, /first-look-at-blinksy/md_gledopto-gl-c-016wl-d.jpg 768w, /first-look-at-blinksy/lg_gledopto-gl-c-016wl-d.jpg 992w, /first-look-at-blinksy/xl_gledopto-gl-c-016wl-d.jpg 1280w, /first-look-at-blinksy/2xl_gledopto-gl-c-016wl-d.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/first-look-at-blinksy/lg_gledopto-gl-c-016wl-d.jpg"  src="/first-look-at-blinksy/gledopto-gl-c-016wl-d.jpg" alt="" loading="lazy" width="1070" height="560" /></div></a></p><p>For this, I made a board support crate: <a href="https://docs.rs/gledopto/0.4/gledopto/index.html"><code>gledopto</code></a>.</p><p>The board support crate provides a few macros to make your life easy, such as a <a href="https://docs.rs/gledopto/0.4/gledopto/macro.board.html"><code>board!</code></a> macro to setup your board, or a <a href="https://docs.rs/gledopto/0.4/gledopto/macro.ws2812.html"><code>ws2812!</code></a> macro that sets up a WS2812 driver using the specific pins for that controller.</p><p><strong>To make even easier, I made a quickstart project template: <a href="https://github.com/ahdinosaur/blinksy-quickstart-gledopto"><code>blinksy-quickstart-gledopto</code></a></strong></p><h3 id="Add-some-LEDs">Add some LEDs<a class="header-anchor" href="#Add-some-LEDs">§</a></h3><p>Now you just need to add LEDs, and away you go.</p><p>If you need an LED supplier recommendation, I’ve only had success with “BTF-Lighting”. You can find them on <a href="https://btf-lighting.aliexpress.com/">AliExpress</a>, <a href="https://www.amazon.com/stores/BTF-LIGHTING/BTF-LIGHTING/page/0FF60378-45DE-44E7-B0D7-8F5CD6478971">Amazon</a>, or on <a href="https://www.btf-lighting.com/">their own website</a>.</p><p>If you need more help, look at <a href="https://quinled.info/addressable-digital-leds/">QuinLED’s helpful guides</a>.</p><p>(Note: I will later add support for <a href="https://quinled.info">QuinLED</a> boards, since they are the best and want to support them. Unfortunately, shipping to New Zealand was too expensive, so I will receive once a friend travels over the Pacific.)</p><h3 id="Hello-LEDs">Hello LEDs<a class="header-anchor" href="#Hello-LEDs">§</a></h3><p>Now, here is our hello world of LEDs:</p><p>A strip of WS2812 LEDs with a scrolling rainbow.</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/1085561502?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Embedded: 1D WS2812 Strip with Rainbow Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::Layout1d,</span><br><span class="line">    layout1d,</span><br><span class="line">    patterns::rainbow::&#123;Rainbow, RainbowParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> gledopto::&#123;board, elapsed, main, ws2812&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[main]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = board!();</span><br><span class="line"></span><br><span class="line">    layout1d!(Layout, <span class="number">60</span> * <span class="number">5</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_1d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Rainbow&gt;(RainbowParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(ws2812!(p, Layout::PIXEL_COUNT))</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    control.<span class="title function_ invoke__">set_brightness</span>(<span class="number">0.2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">elapsed_in_ms</span> = <span class="title function_ invoke__">elapsed</span>().<span class="title function_ invoke__">as_millis</span>();</span><br><span class="line">        control.<span class="title function_ invoke__">tick</span>(elapsed_in_ms).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><p>Or, to show some more:</p><p>A grid of APA102 LEDs with a noise function.</p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/1085561112?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Embedded: 2D APA102 Grid with Noise Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::&#123;Shape2d, Vec2&#125;,</span><br><span class="line">    layout2d,</span><br><span class="line">    patterns::noise::&#123;noise_fns, Noise2d, NoiseParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> gledopto::&#123;apa102, board, elapsed, main&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[main]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = board!();</span><br><span class="line"></span><br><span class="line">    layout2d!(</span><br><span class="line">        Layout,</span><br><span class="line">        [Shape2d::Grid &#123;</span><br><span class="line">            start: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            horizontal_end: Vec2::<span class="title function_ invoke__">new</span>(<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            vertical_end: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., <span class="number">1</span>.),</span><br><span class="line">            horizontal_pixel_count: <span class="number">16</span>,</span><br><span class="line">            vertical_pixel_count: <span class="number">16</span>,</span><br><span class="line">            serpentine: <span class="literal">true</span>,</span><br><span class="line">        &#125;]</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_2d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Noise2d&lt;noise_fns::Perlin&gt;&gt;(NoiseParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(apa102!(p))</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    control.<span class="title function_ invoke__">set_brightness</span>(<span class="number">0.1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">elapsed_in_ms</span> = <span class="title function_ invoke__">elapsed</span>().<span class="title function_ invoke__">as_millis</span>();</span><br><span class="line">        control.<span class="title function_ invoke__">tick</span>(elapsed_in_ms).<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><h3 id="Simulate-on-your-desktop">Simulate on your desktop<a class="header-anchor" href="#Simulate-on-your-desktop">§</a></h3><p>Okay, but let’s say you just want to start now, without a microcontroller, without any LEDs.</p><p>Blinksy also has a way to simulate on your desktop: <a href="https://docs.rs/blinksy-desktop/0.4/blinksy_desktop/"><code>blinksy-desktop</code></a>.</p><p>This provides <a href="https://docs.rs/blinksy-desktop/0.4/blinksy_desktop/driver/index.html">a driver</a> (using <a href="https://github.com/not-fl3/miniquad">miniquad</a>) and <a href="https://docs.rs/blinksy-desktop/0.4/blinksy_desktop/time/index.html">an elapsed time function</a>.</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/1085562226?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="Blinksy Desktop: 2D Grid with Noise Pattern"></div><details><summary>    Click to see code</summary><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> blinksy::&#123;</span><br><span class="line">    layout::&#123;Shape2d, Vec2&#125;,</span><br><span class="line">    layout2d,</span><br><span class="line">    patterns::noise::&#123;noise_fns, Noise2d, NoiseParams&#125;,</span><br><span class="line">    ControlBuilder,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> blinksy_desktop::&#123;</span><br><span class="line">    driver::&#123;Desktop, DesktopError&#125;,</span><br><span class="line">    time::elapsed_in_ms,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">use</span> std::&#123;thread::sleep, time::Duration&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    layout2d!(</span><br><span class="line">        Layout,</span><br><span class="line">        [Shape2d::Grid &#123;</span><br><span class="line">            start: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            horizontal_end: Vec2::<span class="title function_ invoke__">new</span>(<span class="number">1</span>., -<span class="number">1</span>.),</span><br><span class="line">            vertical_end: Vec2::<span class="title function_ invoke__">new</span>(-<span class="number">1</span>., <span class="number">1</span>.),</span><br><span class="line">            horizontal_pixel_count: <span class="number">16</span>,</span><br><span class="line">            vertical_pixel_count: <span class="number">16</span>,</span><br><span class="line">            serpentine: <span class="literal">true</span>,</span><br><span class="line">        &#125;]</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">control</span> = ControlBuilder::<span class="title function_ invoke__">new_2d</span>()</span><br><span class="line">        .with_layout::&lt;Layout&gt;()</span><br><span class="line">        .with_pattern::&lt;Noise2d&lt;noise_fns::Perlin&gt;&gt;(NoiseParams &#123;</span><br><span class="line">            ..<span class="built_in">Default</span>::<span class="title function_ invoke__">default</span>()</span><br><span class="line">        &#125;)</span><br><span class="line">        .<span class="title function_ invoke__">with_driver</span>(Desktop::new_2d::&lt;Layout&gt;())</span><br><span class="line">        .<span class="title function_ invoke__">build</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">let</span> <span class="variable">Err</span>(DesktopError::WindowClosed) = control.<span class="title function_ invoke__">tick</span>(<span class="title function_ invoke__">elapsed_in_ms</span>()) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="title function_ invoke__">sleep</span>(Duration::<span class="title function_ invoke__">from_millis</span>(<span class="number">16</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></details><h3 id="Quickstart-a-project">Quickstart a project<a class="header-anchor" href="#Quickstart-a-project">§</a></h3><p>So wanna jump in now and start your own project?</p><p><strong>I made a quickstart project template: <a href="https://github.com/ahdinosaur/blinksy-quickstart-gledopto"><code>blinksy-quickstart-gledopto</code></a></strong></p><p>You can even simulate on the desktop while your controller and LEDs arrive.</p><h2 id="Thanks">Thanks<a class="header-anchor" href="#Thanks">§</a></h2><p>If you want to help, the best thing to do is use <a href="https://github.com/ahdinosaur/blinksy">Blinksy</a> for your own LED project, and share about your adventures.</p><p>If you want to say something about this post, discuss this <a href="https://github.com/ahdinosaur/meta/issues/3">on GitHub</a>.</p><p>If you want to <a href="https://github.com/ahdinosaur/blinksy">contribute code</a>, please:</p><ul><li>Help port a visual pattern from FastLED or WLED to Blinksy</li><li>Write your own visual pattern</li><li>Help support a new LED chipset</li><li>Help support a new LED controller</li></ul><p>If you want to otherwise support the project, please:</p><ul><li>Star the project on GitHub: <a href="https://github.com/ahdinosaur/blinksy">ahdinosaur/blinksy</a></li><li>Sponsor me on GitHub: <a href="https://github.com/sponsors/ahdinosaur">@ahdinosaur</a></li><li>Subscribe to me on YouTube, to encourage me to live-code Blinksy: <a href="https://www.youtube.com/channel/UCRNri_xZGzROcxGcAYkOhpA">@Make_with_Mikey</a></li></ul><p>Thanks for sharing your attention with me. Have a good one. 💜</p>]]></content>
    
    
    <summary type="html">A Rust no-std no-alloc LED control library for spatial layouts. 🟥🟩🟦</summary>
    
    
    <content src="http://blog.mikey.nz/first-look-at-blinksy/thumbnail.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="led" scheme="http://blog.mikey.nz/tags/led/"/>
    
  </entry>
  
  <entry>
    <title>The Lost Cause: Blue Helmet Modular Systems</title>
    <link href="http://blog.mikey.nz/the-lost-cause-blue-helmet-modular-systems/"/>
    <id>http://blog.mikey.nz/the-lost-cause-blue-helmet-modular-systems/</id>
    <published>2024-10-14T23:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Context">Context<a class="header-anchor" href="#Context">§</a></h2><p>I want to share some inspiring science fiction that paints a picture of a future to believe in. A future with realistic challenges and realistic solutions. Fiction to help ground solarpunk into a vision of action.</p><p><a href="https://en.wikipedia.org/wiki/Cory_Doctorow">Cory Doctorow</a>, in <a href="https://archive.is/XkRnA">“Science fiction and the unforeseeable future: In the 2020s, let’s imagine better things</a>”, says:</p><blockquote><p>The stories we tell about our future affect what we do when the future arrives, so science-fictional tales of weathering the crisis have the makings of a movement that allows us to do so.</p></blockquote><p>In this editorial, he goes on to imagine the foundations for a possible future, one where a “Canadian Miracle” occurs:</p><blockquote><p>As the vast majority of Canadians come to realize the scale of the [climate] crisis, they are finally successful in their demand that their government address it unilaterally, without waiting for other countries to agree.</p><p>…</p><p>Canada goes on a war footing: Full employment is guaranteed to anyone who will work on the energy transition – building wind, tide and solar facilities; power storage systems; electrified transit systems; high-speed rail; and retrofits to existing housing stock for an order-of-magnitude increase in energy and thermal efficiency. All of these are entirely precedented – retrofitting the housing stock is not so different from the job we undertook to purge our homes of lead paint and asbestos, and the cause every bit as urgent.</p><p>How will we pay for it? The same way we paid for the Second World War: spending the money into existence (much easier now that we can do so with a keyboard rather than a printing press), then running a massive campaign to sequester all that money in war bonds so it doesn’t cause inflation.</p><p>The justification for taking such extreme measures is obvious: a 1000 Year Reich is a horror too ghastly to countenance, but rendering our planet incapable of sustaining human life is even worse.</p></blockquote><p>This premise then became a short-story, “The Canadian Miracle”, published in 2023, which <a href="https://reactormag.com/the-canadian-miracle-cory-doctorow/">you can read in full</a>.</p><p>This continues as a book, <a href="https://en.wikipedia.org/wiki/The_Lost_Cause_(novel)">“The Lost Cause”</a>, also published in 2023, about near-future Burbank, California.</p><p>I highly recommend reading this editorial, short-story, and book in full. They are without a doubt my favorite contemporary author’s take on science fiction.</p><p>I want to share excerpts from this future that are specific to the <a href="https://villagekit.com/vision">Village Kit vision</a>: a modular system for building the practical infrastructure we need to live, amidst a climate crisis.</p><p>In this future, <a href="https://en.wikipedia.org/wiki/United_Nations_peacekeeping">Blue Helmet</a> peacekeeping operations have become climate crisis responders. When cities flood, sea levels rise, forests burn, and so on, the Blue Helmets are those who respond to support new waves of climate refugees. Except, rather than the temporary refugee camps of today, they developed systems to build new permanent refugee cities.</p><p>I want to share these Blue Helmet modular systems, as an inspiring view into a practical future.</p><h2 id="“The-Lost-Cause”">“The Lost Cause”<a class="header-anchor" href="#“The-Lost-Cause”">§</a></h2><p>The world of “The Lost Cause” is set in the 2050’s. The main character, Brooks Palazzo, is in the same generation as those who are born today.</p><h3 id="Page-146-149">Page 146-149:<a class="header-anchor" href="#Page-146-149">§</a></h3><p>Let’s start with quotes from page 146 to 149.</p><p>Brooks pulls a job from the Jobs Guarantee: a federally funded, locally administered government program that provides good jobs at inclusive wages that serve community needs proposed by community groups and approved by local governments.</p><p>Here we see what a future construction process might look like, accessible even to those who aren’t professionally trained.</p><blockquote><p>Preparing the lot for the new low-rise took three days: working under a master surveyor, we staked it out, got it leveled, and installed the foundation slab.</p><p>…</p><p>The original dwelling was a two bed / one bath on a one-acre lot. The new low-rise would be three stories tall, with six two-bedroom apartments and one four-bedroom apartment on the top story, and no garage.</p><p>…</p><p>The real excitement started when the panels rolled up. They came from a factory in Mojave, the same one that was providing the materials for the relocation of San Juan Capistrano. The sinterers only fused the concrete and polystyrene balls when the grid was saturated, scavenging solar energy that would otherwise go to waste.</p><p>…</p><p>I know it’s weird to nerd out about blocks, but I was a total block nerd and I knew that the Mojave plant had its own on-site research team that was always tweaking the formula, making them lighter and stronger and more resilient to temperature extremes and quake stresses. I was addicted to their<br>videos of test buildings being subjected to the most awesome tests, watching wrecking balls bounce off them and shake platforms making them shimmy without cracking.</p><p>…</p><p>I scanned the QR code on the tie-down with my screen to look at the manifest again. The panels—8′ × 4′ concrete sheets—weighed less than fifty kilos each.</p><p>…</p><p>A couple of the work-crew-mates had spent a Blue Helmet year in San Juan Capistrano and knew the panel construction technique backward and forward. Our foremen were a couple of the refus from the<br>caravan who’d been general contractors back in the San Joaquin Valley. The rest of us had watched the training videos and done the online certification and in theory we knew what we were doing—the panel system was designed to be dead-stupid simple in any event.</p><p>…</p><p>Naturally, there was a lot of last-minute delay between the time we thought we’d start standing up panels and the first panel going in: triple-checking everything was level, comparing the panels with the plans to make sure the ones with built-in conduit for plumbing, HVAC, and power services were lined up in the right spot, and so on. But finally, around eight thirty, we were ready to go.</p><p>…</p><p>The first panel went in like butter, slotting perfectly into the groove and pin system in the foundation slab. It was now structurally sound, and as the tightening crew gave each of its lock bolts a half turn, it became rigidly fixed in place. This was the keystone, and we cheered as it went up but then quickly broke into the subteams that the apps sorted us into, each with its own piece in the logical path. With twenty-five people, we could finish the main work on the first floor by sunset, and while the upper stories would take longer, that would let interior crews work on the first floor, adding fixatives to the panel intersections, connecting and testing the plumbing,gas, HVAC, and power. Give us ten days, we’d be ready for paint and furniture. It was going to be so amazing, like a magic trick.</p><p>And then…</p></blockquote><p>Something unexpectedly bad happens, outside the scope of the modular system. I invite you to read the book to follow the actual storyline. 😀</p><h3 id="Page-207-210">Page 207-210<a class="header-anchor" href="#Page-207-210">§</a></h3><p>Next up, Brooks imagines if his grandfather’s house was given away to the city for infill: a process of using land for high-density development.</p><p>Here we see what a future housing design process, turned city planning, might look like.</p><blockquote><p>The brainstorming session made me think they were just dreamers, but Phuong and her housemates were all ex–Blue Helmets. They were used to getting up in the morning, meeting with a community group about the destiny of some brownfield site, and building a new residence there in three days flat.</p><p>Don and Arina got onto some next-generation modular slab designs and then Arina realized she’d been in the field with the woman who’d led the project to create them, so they bridged her in.</p><p>Miguel had a contact in Simi Valley who coordinated GND Housing Guarantee builds for the whole region, and he took all of ten minutes to find us a flock of cranes, bulldozers, forklifts, and other heavy machines.</p><p>Meanwhile, Phuong was crawling these databases of parameterized build plans, dragging buildings of various sizes onto Gramps’s lot to check out the shadows, seismics, and whether they needed more water infrastructure than the city could provide from the nearby mains and sewer.</p><p>I had said “high-rise” but honestly, I’d never really thought about anything more than five or six stories. The tallest building in the neighborhood was only three stories tall. Plus, even with all my GND trufan energy, I couldn’t imagine a ragtag group of guerrilla builders erecting an honest-to-goodness skyscraper. Phuong had other ideas, and she clicked and dragged a bunch of legit high-rises onto her rendering of the lot before settling on a building that could be scaled up as high as thirty floors, though she’d settled for twenty, earmarking both the top and bottom floors for community spaces.</p><p>“How’re you going to sink the pilings?” Don asked, looking over at her screen. She tapped and showed him a picture of a machine with an auger bit on it that was insane, like something you’d use to drill to the center of the Earth.</p><p>Don laughed. I laughed too. It was absurd.</p><p>“That’s two, three days of drilling,” Miguel said. He tapped at Phuong’s screen to bring up a time-critical path chart. He dragged the top story of the building down to eight floors and we watched as the time ticked down.</p><p>“Ninth story is always the killer,” he said.</p><p>Phuong stuck her lower lip out. “I want a skyscraper!”</p><p>“I know baby, I know,” Don said, distractedly, tweaking the floor plans to create more flexible configurations, either a three-bedroom and a bachelor on each floor, or two two-bedrooms. I mirrored his work on my screen and started tapping around, finding a mode that let me tap into the city street plan and start dragging that around, and before long, I was transforming the whole Verdugo corridor into high-rises with a light-rail line down the middle of it, anchored to a subway station where the old strip mall was currently languishing, part-occupied, mostly used as a skate park and a weekend craft market.</p><p>I zoomed out and saw that my own crude high-density corridor was being polished by Phuong’s housemates, who abandoned their work on the hypothetical high-rise we were going to build on Gramps’s lot in order to create green roofs, vertical farms, parkettes, a community center added to the main branch library at Buena Vista. I watched with my mouth open as they worked together, like musicians improvising a jam session, except they were improvising a whole neighborhood, and I could tab over to the spreadsheets where there were build plans, bills of materials, critical path and building-code variances we’d have to file for.</p><p>Jacob shoulder-surfed me. “It’s cool, isn’t it? It’s just Blue Helmet stuff, though; kind of thing we used to do in-country, helping people think through what their neighborhoods could be. We’d do a couple training sessions, turn ’em loose for a week to come up with designs, get into revert-wars, and then we workshop ’em and do it again. A month later, you’ve got some incredible designs, and all that stuff gets trained back into the model so it hints the next group who try it. That’s why it’s going so fast—it’s hella trained.”</p><p>“Don’t say ‘hella,’” Arina said. “It makes you sound stupid.”</p><p>“I am stupid,” he said, and crossed his eyes and stuck out his tongue.</p><p>Then Arina took away his screen and wiped out and reshaped a granny flat he’d been sticking on top of a garage off California, tapping the seismic rating box when she was done.</p><p>“There, not a death trap anymore.”</p><p>I grabbed a screen and dug in, snuggling up to Phuong, who’d sometimes look over at my screen and show me a faster or more elegant way to do what I was trying to do, and the neighborhood took on a polish and veneer that made it seem like a real architect’s rendering. I was flying around when I realized I was looking at the building we’d started with. Gramps’s house. My house.</p><p>It was beautiful, eight stories tall, roofed over with photovoltaics and a couple of eggbeaters, a courtyard with vegetable beds and ornamentals. The apartments were neat, thoughtful, weatherized for cross breezes, with charming, self-adjusting shades that kept them cool.</p><p>I flew through it, one apartment after another, the utility spaces and infrastructure, and … Phuong … guided … as we flew through the house, through the neighborhood, through the city, flying off the edge of the map into the undefined places of Cartesian grid lines.</p></blockquote><h3 id="Page-243-247">Page 243-247<a class="header-anchor" href="#Page-243-247">§</a></h3><p>At this point in the story, the characters take the city plan that was started in the above scene, and present it to the city as a pop-up festival.</p><p>Here again, we see the possibility of future city planning, how we could re-imagine civic engagement and community consultation. We also see the importance of future social media moderation strategies.</p><blockquote><p>We hustled. By the time the first cops showed up we were curtain-raiser ready, with all our booths up and the big screens shook out and powered up, showing displays of the new Burbank, first cooked up in Phuong’s living room and then wikified and edited by a couple thousand Burbankers, mostly affinity group members and their friends, whose designs got polished up by infill urbanists all around the world.</p><p>…</p><p>Every booth displayed a different aspect of the New Burbank. One group had done transit, another had done libraries, another had done schools. The big action was in the neighborhoods, displayed as big-screen flythroughs that you could view or modify, either from your own screen or by just dragging stuff around on the big screens. The kids did a lot of the latter, while their grown-ups were more interested in the former. I eavesdropped on a lot of conversations, hopping from booth to booth, and I started to see a pattern:</p><p>Rando: “Come on, you’ve gotta be kidding me. This is crazy, it’s not what the city is about.”<br>Guerrilla planner: “Did you see this park?” (or stadium, or rink, or subway station, or library, or business strip)</p><p>Rando: “Sure, sure, but come on. Be serious.”<br>Guerrilla planner: “Did you see the lake? The lake’s super cool, it’s part of the runoff system. All the road grades run toward it; if there’s a flood it’ll absorb all the water and then most of that will end up back in the water table.”</p><p>Rando: “Yeah, that’s cool, but come on. Be serious.”<br>Guerrilla planner: “Did you see the solar capacity? Total energy independence, and we’ve got this concrete factory over here, it’ll just sinter prefab any time there’s more power in the grid than we can use. Then any time someone goes on the job guarantee, one of the gigs’ll be building one of these buildings, using that prefab. It’s carbon-neutral mass-scale construction—”</p><p>Rando: “That <em>is</em> cool. How the hell does that work?”<br>And then they were off. The excitement was infectious</p><p>… The people who couldn’t get to Burbank, especially other Californians who’d been in a fury about the injunctions and the bombings and the seizures, started building out our map, overlaying huge swaths of LA and the San Fernando Valley with their own dreams.</p><p>Even better: the Magas fucking hated it. This was culture-war clickbait times a billion, and it seemed like half the world’s reactionaries, trolls, bots, and assholes were determined to vandalize our virtual space (we had good anti-vandal, plus a swift revert-squad that the little kids on Magnolia joined in droves, cackling as their tiny hands smacked the screens to undo the damage faster than the irrelevant hunt-and-peck typists of the fallen past could wreak it).</p></blockquote><h3 id="Page-272">Page 272<a class="header-anchor" href="#Page-272">§</a></h3><p>At this point in the story, Brooks wants to follow through on his plan to tear down his grandfather’s house and building high-density housing in its place. To hell or high water.</p><blockquote><p>“You want to blow up City Hall?”<br>“No,” I said. “I want to blow up this house.”</p><p>Not literally, of course. But knocking down a house and building an eightplex is a solved problem, as they say on the DSA socials. It takes a lot of heavy equipment and prefab slabs and a lot of people, especially if you’re in a hurry. But with every project in limbo, we had all the materials and equipment we needed standing by and most of it was in depots that were managed by friends and friends-of-friends, and there were also lots of friends and friends-of-friends standing by with nothing to do and a lot of pent-up nervous energy.</p></blockquote><h3 id="Page-275">Page 275<a class="header-anchor" href="#Page-275">§</a></h3><p>Here we see the participatory aspect of this future building system, anyone can contribute with the skills they have available.</p><blockquote><p>We’d told Dolores and company what we were thinking of over lunch and asked them what they thought. We made sure they understood that they had a veto over this, because we weren’t about to turn them out into the smoke while we did our weird-ass shit. They were completely cool about it, though, excited once we’d conveyed what we had in mind and wanting to help out. They found friends who talked their host families into letting them double up, and pored over the floor plans for the new building, talking about how they’d furnish their places. Antonio was a finish carpenter and pulled up pictures of some of the kitchen remodels he’d done and we all noodled around with superimposing them on the renders of the interiors.</p><p>Phuong, meanwhile, had been exchanging disappearing/deniable messages with friends around the DSA, which led her to Tony Yiannopoulos, who found us a mole inside the Department of Public Works —a former shop steward who got promoted to management but never switched sides. After a couple of texts, she jumped on video and did this mind-meld thing with Phuong, ingesting her project manifest and locating available equipment at DPW lots all around the city. There was a lot of idle gear in town, thanks to the double whammy of the fires and the moratorium on emergency house construction—the city had requisitioned a lot of extra equipment from the county when the caravan first started heading our way.</p></blockquote><h3 id="Page-277">Page 277<a class="header-anchor" href="#Page-277">§</a></h3><p>When building an eightplex is simply a bill of materials.</p><blockquote><p>I rattled the gate and shouted, “Who brought the bolt cutters?”</p><p>A figure loomed out of the smoke on the other side of the gate, bulky and shambling in a grayish, worn hazmat suit. “Don’t you cut my goddamned gate, child, or I’ll make you whittle a replacement.” The voice, muffled by the mask, was gravely and gruff, but affectionate. “You Brooks?”</p><p>“That’s me,” I said.</p><p>“You know how to drive a forklift?”</p><p>I shrugged. “Just a little I picked up on jobsites.”</p><p>“Uh-huh. How about a crane? Backhoe? Excavator? Boring machine?”</p><p>“Nope,” I said. “But they do.” I jerked my thumb over my shoulder at the gang we’d organized over the DSA socials. There were fifteen of us, and with current certifications for all those and more.</p><p>“How about a tractor cab?”</p><p>“Uh,” I said.</p><p>He snorted. “Trick question, boy. Thing drives itself. It’s got fifteen hundred prefab slabs on it and all the electric and plumbing fittings on your bill of materials.”</p><p>“Bruce,” I said, “this is just amazing.”</p></blockquote><h3 id="Page-282">Page 282<a class="header-anchor" href="#Page-282">§</a></h3><p>When everything fits into interlocking components.</p><blockquote><p>Blue Helmet prefab buildings are fast, but word gets around faster.</p><p>We were able to recycle the foundation slab, which meant that once the pilings were in, one crew could go to work fitting and locking panels while a second crew added structural members for the next stage. Wilmar was in charge of double-checking the building against the plans, making sure that panels with inset plumbing and electrical components were seated correctly and had good interconnects with their adjacent panels.</p></blockquote><h3 id="Page-286-287">Page 286-287<a class="header-anchor" href="#Page-286-287">§</a></h3><p>When these interlocking components can self-validate their correctness.</p><blockquote><p>Over the course of that day, progress had been amazing, then agonizing, then amazing again. The first two rows of slabs clicked into place quickly, accelerating as the work crews got into the rhythm of the job. There’d been a moment when I stepped back from sealing a slab into place and looked around and realized that there was a brand-new structure there, just like that, a magic trick in three dimensions.</p><p>Then there’d been a long period of agonizing slowness as all the services were checked and rechecked—electrics, plumbing, data. It all had interconnect and integrity sensors built in, but we had to activate them and get them initialized and chained before they could start working. Time crawled as we troubleshot persnickety problems, sometimes using Blue Helmet socials to conference in experts who could suggest fixes to thorny problems.</p><p>Then everything lit green and crews went to work snapping on the joists and locking them in, laying down the floor slabs, and putting in scaffolding for the second story and we were off to the races again, half of the next story racing around the building’s edge even as a crew added exterior and interior doors and other fittings to the first floor. We even got the plumbing working, and the jobsite got a sink and a toilet. With the door closed, the windows fitted, and the first-floor ceiling in place, we got filters running and then we had an indoor space with breathable air, just like that.</p></blockquote><h3 id="Page-293">Page 293<a class="header-anchor" href="#Page-293">§</a></h3><p>Imagine building the frame of a house in 48 hours.</p><blockquote><p>“How did you keep this all under wraps while you built it?”</p><p>“We didn’t,” I said. “I mean, we did, a little, but it only took forty-eight hours.”</p><p>“Forty-eight hours for what?”</p><p>“For you to show up.”</p><p>I watched that land on her. “Wait … You built this over the last two days?”</p><p>“Well, the first day was mostly demolition and hauling. Most of this is day two. Phuong tells me it’ll take forever to finish the interiors, but like she said, we figure we can do all the structural work including the roof by tonight.”</p><p>Olivia opened her mouth. Closed it. Repeated the procedure. “Bullshit.”</p><p>Phuong laughed. “Girl, it’s only because you went into planning instead of going overseas with a Blue Helmet brigade. This is how we do it, when we’re working fast after a flood or a famine—the time it used to take to put up a bunch of tents and build some shithole refugee camp, now we can build a whole fucking city. We could do it here, if we had the same rules of engagement as we do in the field.”</p></blockquote><h3 id="Page-320">Page 320<a class="header-anchor" href="#Page-320">§</a></h3><p>In this future, building infrastructure become collaboration amongst peers, and the tooling made reflects that.</p><blockquote><p>There’s a Blue Helmet truism that the last 10 percent of any project takes 90 percent of the time, and that was certainly my experience with the building that had once been Gramps’s house. The early stages had involved standard parts—slabs, infrastructure, glazing, insulation—but now that we were doing fine work, things had to be just right—exactly right modular kitchen cabinets, the right sliding doors for balconies and locks for internal doors. More than half of the work was just hitting the screens, wrangling with other crews nearby to see who had the part you needed and then figuring out how to make rendezvous and acquire it.</p><p>But over the next four days, things took shape, and a couple of the apartments got real furniture, a mix of prefabs and donated pieces, and it was wild to see the rough structure I’d left a week before with my lungs on fire and my arms so heavy I could barely lift them now looking ready for human habitation. And habitate it we did, as parts of our crew left to work on other buildings that needed more work, until there were fewer than a dozen of us on-site at any moment, many of us couples like Phuong and me, so that we ended up with our own temporary, personal apartments (though people on the third and fourth floors had to borrow facilities from their downstairs neighbors because the city hadn’t been able to upgrade our main<br>water service yet).</p></blockquote><h2 id="Reflections">Reflections<a class="header-anchor" href="#Reflections">§</a></h2><p>I want to live in this world.</p><p>If you haven’t yet read “The Lost Cause”, <a href="https://craphound.com/">get yourself a copy</a> and live in this world for a time.</p><p>There’s much more in the book than I can reasonably capture on this page, see for yourself and let me know what you see.</p><p>I want to make this future world a reality, that’s why I’m working on <a href="https://villagekit.com">Village Kit</a>. Join us?</p><p>What do you think? <a href="https://discuss.villagekit.com/t/the-lost-cause-blue-helmet-modular-systems/84">Discuss this further on the Village Kit forum</a></p>]]></content>
    
    
    <summary type="html">I want to share some inspiring science fiction that paints a picture of a future to believe in. A future with realistic challenges and realistic solutions. Fiction to help ground solarpunk into a vision of action.</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>EVERYTHING IS DEEPLY INTERTWINGLED</title>
    <link href="http://blog.mikey.nz/everything-is-deeply-intertwingled/"/>
    <id>http://blog.mikey.nz/everything-is-deeply-intertwingled/</id>
    <published>2023-05-31T12:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p><div class="image-wrapper"><img srcset="/everything-is-deeply-intertwingled/sm_douglas-engelbart-keyboard.jpg 480w, /everything-is-deeply-intertwingled/md_douglas-engelbart-keyboard.jpg 768w, /everything-is-deeply-intertwingled/lg_douglas-engelbart-keyboard.jpg 992w, /everything-is-deeply-intertwingled/xl_douglas-engelbart-keyboard.jpg 1280w, /everything-is-deeply-intertwingled/2xl_douglas-engelbart-keyboard.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/everything-is-deeply-intertwingled/lg_douglas-engelbart-keyboard.jpg"  src="/everything-is-deeply-intertwingled/douglas-engelbart-keyboard.jpg" alt="" loading="lazy" width="1536" height="1090" /></div></p><p>I made a collection of videos to showcase the magic alchemy of computation. ✨</p><p>What is computing if you take away all hardware in front of your eyes? 🌱</p><p>What does computing mean to humanity? 🌻</p><ul><li>PeerTube: <a href="https://tube.arthack.nz/c/intertwingled/">@intertwingled@tube.arthack.nz</a></li><li>GitHub: <a href="https://github.com/ahdinosaur/intertwingled">ahdinosaur/intertwingled</a></li></ul><p>Not sure where to start?</p><ul><li><a href="https://tube.arthack.nz/videos/watch/942ce12d-49b6-41f2-9a1a-6f42acfc775c">(2013) The Future of Programming : Bret Victor</a></li><li><a href="https://tube.arthack.nz/videos/watch/4d61c202-bef0-4da5-81c3-2d1b45622e53">(1997) The Computer Revolution Hasn’t Happened Yet : Alan Kay</a></li></ul>]]></content>
    
    
    <summary type="html">I made a collection of videos to showcase the magic alchemy of computation. ✨</summary>
    
    
    <content src="http://blog.mikey.nz/everything-is-deeply-intertwingled/douglas-engelbart-keyboard.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>The Made Up Game</title>
    <link href="http://blog.mikey.nz/the-made-up-game/"/>
    <id>http://blog.mikey.nz/the-made-up-game/</id>
    <published>2023-04-23T00:46:51.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Role-playing-a-computer-🤖">Role-playing a computer 🤖<a class="header-anchor" href="#Role-playing-a-computer-🤖">§</a></h2><p>For some reason, I don’t like playing <a href="https://en.wikipedia.org/wiki/Tabletop_game">tabletop games</a> where there’s a pre-defined set of technical rules. I feel like I end up being a computer. I enjoy playing with computers, I don’t enjoy playing as a computer simulator.</p><p>So one day, nearly a decade ago, some friends stumbled upon game we called the “Made Up Game”, and I discovered my favorite genre of game: games where you play with changing the rules of the game.</p><p><div class="image-wrapper"><img src="/the-made-up-game/calvinball-1990-may-05.gif" alt="" loading="lazy" /></div></p><p>So here’s the Made Up Game: a creative <a href="https://en.wikipedia.org/wiki/Card_game">card game</a> where you make up the rules.</p><h2 id="How-to-start-playing-the-Made-Up-Game-🏓">How to start playing the Made Up Game 🏓<a class="header-anchor" href="#How-to-start-playing-the-Made-Up-Game-🏓">§</a></h2><p>While the game is all about changing the rules, we need a set of bootstrap rules to get the game started:</p><blockquote><h3 id="Setup">Setup<a class="header-anchor" href="#Setup">§</a></h3><ol><li>Shuffle a deck of cards</li><li>Deal 5 cards to each player</li><li>Place the remaining pile of cards face-down</li><li>Turn the top card up and place it next to the face-down pile</li></ol><h3 id="Step">Step<a class="header-anchor" href="#Step">§</a></h3><p>In clockwise direction, for each player:</p><ol><li>Pick up card from the face-down pile</li><li>Play a card onto the face-up pile</li><li>Makes a change to the rules</li></ol><h3 id="Lose-Condition">Lose Condition<a class="header-anchor" href="#Lose-Condition">§</a></h3><p>If the next player can’t play the game within the current rules, everyone loses.</p><h3 id="Win-condition">Win condition<a class="header-anchor" href="#Win-condition">§</a></h3><p>If all the cards are face-up on the table.</p></blockquote><p>Source: <a href="https://github.com/ahdinosaur/made-up-game"><code>ahdinosaur/made-up-game</code></a></p><h2 id="Your-first-problem-to-solve-as-a-team-👥">Your first problem to solve as a team 👥<a class="header-anchor" href="#Your-first-problem-to-solve-as-a-team-👥">§</a></h2><p>Something you may notice is that the bootstrap rules start with a bug:</p><ul><li>At the beginning of every turn, the player must pickup a card from the face-down pile.</li><li>And the lose condition is if the next player can’t play within the current rules.</li><li>So once the face-down pile runs out, the next player can’t play, and you lose.</li></ul><p>So if you want to survive, you must change the rules to accommodate for this.</p><p>How? Well that’s up to you!</p><h2 id="Observations-on-playing-the-Made-Up-Game-🔍">Observations on playing the Made Up Game 🔍<a class="header-anchor" href="#Observations-on-playing-the-Made-Up-Game-🔍">§</a></h2><p>Having played the Made Up Game a few times, every game is unique, no game is ever the same.</p><p><div class="image-wrapper"><img srcset="/the-made-up-game/sm_calvinball-1990-may-27.jpg 480w, /the-made-up-game/md_calvinball-1990-may-27.jpg 768w, /the-made-up-game/lg_calvinball-1990-may-27.jpg 992w, /the-made-up-game/xl_calvinball-1990-may-27.jpg 1280w, /the-made-up-game/2xl_calvinball-1990-may-27.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/the-made-up-game/lg_calvinball-1990-may-27.jpg"  src="/the-made-up-game/calvinball-1990-may-27.jpg" alt="" loading="lazy" width="900" height="637" /></div></p><p>Here are some example styles of rules I’ve seen:</p><p>(<a href="https://github.com/ahdinosaur/made-up-game#example-rules">More examples</a>)</p><h3 id="Card-games-♠️">Card games ♠️<a class="header-anchor" href="#Card-games-♠️">§</a></h3><p>Since we’re using a deck of cards, why not bring in typical card game rules?</p><p>For example:</p><blockquote><p>If a player plays a card of the same suit as the previous card played, then the next player must draw 2 more cards on their turn.</p></blockquote><h3 id="Story-games-👑">Story games 👑<a class="header-anchor" href="#Story-games-👑">§</a></h3><p>What if we used the cards to tell stories?</p><p>For example:</p><blockquote><p>After you play a card, you tell a story about your card. Face cards are individuals and number cards are groups.</p></blockquote><h3 id="Stack-games-🥪">Stack games 🥪<a class="header-anchor" href="#Stack-games-🥪">§</a></h3><p>What if we made something from these face-up and face-down piles of cards?</p><p>For example:</p><blockquote><ul><li>Each player can play a card anywhere to create a new face-up pile.</li><li>Each player can draw a card from any available face-down pile.</li></ul></blockquote><h3 id="Combat-games-⚔">Combat games ⚔<a class="header-anchor" href="#Combat-games-⚔">§</a></h3><p>Feeling too collaborative? Why not add in a combat system to the game?</p><blockquote><p>Each player has 10 “health points”.</p></blockquote><p>What is a “health point”? How do we lose “health points”? What happens if we run out of “health points”?</p><p>I don’t know, I don’t make the rules, that’s for you to decide in future rules.</p><h3 id="Cursed-games-👻">Cursed games 👻<a class="header-anchor" href="#Cursed-games-👻">§</a></h3><p>Feeling too normal? Why not bring in the occult?</p><blockquote><p>If you play an odd card, that pile becomes “cursed”.</p></blockquote><p>What does “cursed” even mean? Shrug, that’s for you to decide as you play with more rules.</p><h2 id="Anything-is-possible-in-the-Made-Up-Game-✨">Anything is possible in the Made Up Game ✨<a class="header-anchor" href="#Anything-is-possible-in-the-Made-Up-Game-✨">§</a></h2><p>There’s no limit to what rule changes are allowed, so really any game using cards as props is possible.</p><p>Think: <a href="https://en.wikipedia.org/wiki/Abstract_strategy_game">strategy</a>, <a href="https://en.wikipedia.org/wiki/Tabletop_role-playing_game">role-playing</a>, <a href="https://en.wikipedia.org/wiki/Abstract_strategy_game">adventure</a>, <a href="https://en.wikipedia.org/wiki/Patience_(game)">solitaire</a>, <a href="https://en.wikipedia.org/wiki/List_of_shedding-type_games">shedding</a>, <a href="https://en.wikipedia.org/wiki/Trick-taking_game">trick-taking</a>, <a href="https://en.wikipedia.org/wiki/Matching_game">matching</a>, <a href="https://en.wikipedia.org/wiki/Deck-building_game">deck-building</a>, and more.</p><p>How might you introduce a rule from another game into the Made Up Game?</p><h2 id="More-rule-changing-games-📚">More rule-changing games 📚<a class="header-anchor" href="#More-rule-changing-games-📚">§</a></h2><p><div class="image-wrapper"><img srcset="/the-made-up-game/sm_calvinball-1990-august-26.jpg 480w, /the-made-up-game/md_calvinball-1990-august-26.jpg 768w, /the-made-up-game/lg_calvinball-1990-august-26.jpg 992w, /the-made-up-game/xl_calvinball-1990-august-26.jpg 1280w, /the-made-up-game/2xl_calvinball-1990-august-26.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/the-made-up-game/lg_calvinball-1990-august-26.jpg"  src="/the-made-up-game/calvinball-1990-august-26.jpg" alt="" loading="lazy" width="900" height="637" /></div></p><h3 id="Nomic-⚖️">Nomic ⚖️<a class="header-anchor" href="#Nomic-⚖️">§</a></h3><p>Since the Made Up Game, some internet friends introduced me to <a href="https://en.wikipedia.org/wiki/Nomic">“Nomic”</a>, a rule changing game created in 1982.</p><blockquote><p>Nomic is a game in which changing the rules is a move. In that respect it differs from almost every other game. The primary activity of Nomic is proposing changes in the rules, debating the wisdom of changing them in that way, voting on the changes, deciding what can and cannot be done afterwards, and doing it. Even this core of the game, of course, can be changed.</p><ul><li>Peter Suber, The Paradox of Self-Amendment</li></ul></blockquote><p>We tried playing a game of Nomic together online, but we didn’t get too far. This was only my limited experience, but the rules felt more serious and bureaucratic. I don’t get joy from being a rule lawyer, I like to be more silly and loose with my rule playing.</p><p>I’m happy Nomic exists and maybe I should try again with a different starter culture.</p><p>If I play again, I want some fun lunacy with my rules, for example:</p><ul><li>Introduce celestial bodies as a foundation for “magic”, starting with the moon.</li><li>Moonlight somehow determines how much “magical power” that players have access to.</li><li>Create an arbitrary and artificial tension between players where some players have more “magical power” when the moon is brighter and some players have more “magical power” when the moon is darker.</li><li>Create a cooperative challenge where every cycle of the moon causes the moon to be closer, slowly increasing the environment of “magical power” to dangerous levels.</li></ul><p>🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔</p><h3 id="The-Counting-Game-🔢">The Counting Game 🔢<a class="header-anchor" href="#The-Counting-Game-🔢">§</a></h3><p>After playing the Made Up Game for some time, I later made a more casual rule-changing game based on a drinking game, which I call the “Counting Game”.</p><p>TODO: Link to a post about the Counting Game.</p><h2 id="Good-luck-🙏">Good luck! 🙏<a class="header-anchor" href="#Good-luck-🙏">§</a></h2><p>Thanks for reading!</p><p>If you do play the Made Up Game: good luck and have fun!</p><p>Please contribute anything you learn to <a href="https://github.com/ahdinosaur/made-up-game"><code>ahdinosaur/made-up-game</code></a>.</p>]]></content>
    
    
    <summary type="html">One day, nearly a decade ago, some friends stumbled upon game we called the &quot;Made Up Game&quot;, and I discovered my favorite genre of game: games where you play with changing the rules of the game.</summary>
    
    
    <content src="http://blog.mikey.nz/the-made-up-game/calvinball-1990-may-05.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>How to dance with embedded Rust generics</title>
    <link href="http://blog.mikey.nz/how-to-dance-with-embedded-rust-generics/"/>
    <id>http://blog.mikey.nz/how-to-dance-with-embedded-rust-generics/</id>
    <published>2023-04-05T02:15:54.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>As part of <a href="https://villagekit.com">my work</a> on <a href="https://github.com/villagekit/gridbot-tahi">Grid Bot “Tahi”</a>, I <em>finally</em> figured out how to make <a href="https://github.com/villagekit/robokit">the code for my robot re-usable as a library</a>. Since to do this I needed to go on a deep journey into understanding Rust generic types, I thought I might share my learnings.</p><span id="more"></span><h2 id="Disclaimer-⚠️">Disclaimer ⚠️<a class="header-anchor" href="#Disclaimer-⚠️">§</a></h2><p>As a quick disclaimer: This post is not a basic introduction to embedded Rust. For a basic introduction to embedded Rust, see <a href="https://docs.rust-embedded.org/">the embedded Rust Bookshelf</a> for resources.</p><p>This post is instead about solving a niche problem: how to create a re-usable library where users (developers) can setup their own hardware devices (LEDs, linear axes, spindles, relays, etc) and then use a command language (like <a href="https://en.wikipedia.org/wiki/G-code">G-Code</a>) to control those devices, all in an Rust-y embedded-y device-agnostic type-safe heap-free way.</p><h2 id="The-embedded-Rust-world-🗺️">The embedded Rust world 🗺️<a class="header-anchor" href="#The-embedded-Rust-world-🗺️">§</a></h2><p>Before we begin, here’s a quick recap of <a href="https://github.com/rust-embedded/awesome-embedded-rust">the embedded Rust world</a>:</p><ul><li>Rust can compile to any <a href="https://doc.rust-lang.org/rustc/targets/index.html"><code>target</code></a> supported by <a href="https://en.wikipedia.org/wiki/LLVM">LLVM</a>, so <a href="https://doc.rust-lang.org/rustc/platform-support.html">most everything</a>.</li><li>You can tell Rust to be in <a href="https://docs.rust-embedded.org/book/intro/no-std.html">“<code>#[no_std]</code>”</a> mode and your code will not load the standard library (<code>std</code>)  or data structures that depend on heap allocations (<code>alloc</code>).<ul><li>To use data structures that depend on heap allocations (<code>alloc</code>), you can then BYO (bring your own) allocator, such as <a href="https://github.com/rust-embedded/embedded-alloc"><code>embedded-alloc</code></a>.</li></ul></li><li>Each processor architecture usually has a Rust crate (module) for <a href="https://docs.rust-embedded.org/book/start/registers.html#micro-architecture-crate">low-level access to the processor</a><ul><li>For example: <a href="https://github.com/rust-embedded/cortex-m"><code>cortex-m</code></a> for ARM processors, <a href="https://github.com/rust-embedded/riscv"><code>riscv</code></a> for RISC-V processors, etc.</li><li>Note: most embedded chips use an ARM processor</li></ul></li><li>Each chip family has a <a href="https://docs.rust-embedded.org/book/start/registers.html#using-a-peripheral-access-crate-pac">“Peripheral Access Crate”</a> for low level control of peripherals<ul><li>Hardware manufacturers provide an SVD file (System View Description), which define how the hardware’s (magic) memory addresses are mapped to peripheral registers, <a href="https://github.com/rust-embedded/svd2rust"><code>svd2rust</code></a> converts these to a type-safe Rust interface, so you can only use the registers in a safe way.</li><li>For example, the <a href="https://github.com/stm32-rs/stm32-rs"><code>stm32-rs</code></a> for STM32 microcontrollers, <a href="https://github.com/nrf-rs/nrf-pacs"><code>nrf-pacs</code></a> for nRF microcontrollers</li></ul></li><li>Each chip family then also has a <a href="https://docs.rust-embedded.org/book/start/registers.html#using-a-hal-crate">“Hardware Abstraction Layer” crate</a> for higher level control of peripherals<ul><li>To provide a foundation for building device-agnostic hardware drivers, <a href="https://github.com/rust-embedded/embedded-hal"><code>embedded-hal</code></a> provides <a href="https://doc.rust-lang.org/book/ch10-02-traits.html">Traits</a> (abstract interfaces) for (most) hardware abstractions.</li><li>Each each device has their own <code>xxx-hal</code> which provides the specific hardware implementations for these abstract interfaces.</li><li>For example, I’m using a <a href="https://nz.element14.com/stmicroelectronics/nucleo-f767zi/dev-board-nucleo-32-mcu/dp/2546569">Nucleo-F767ZI</a>, which is supported by <a href="https://github.com/stm32-rs/stm32f7xx-hal"><code>stm32f7xx-hal</code></a>.</li><li>Another example is the <code>ESP32-C3</code> supported by <a href="https://github.com/esp-rs/esp-hal/tree/main/esp32c3-hal#getting-started"><code>es32c3-hal</code></a></li></ul></li></ul><p>(For an in-depth adventure into porting Rust to a chip, see <a href="https://noxim.xyz/blog/rust-ch32v003/">Rust on the CH32V003</a>)</p><p>In our quest, we will be building something device-agnostic using the generic HAL (Hardware Abstraction Layer) traits. We won’t need to worry much about the lower level details, things just work.</p><h2 id="A-device-agnostic-Led-interface-🟢">A device-agnostic <code>Led</code> interface 🟢<a class="header-anchor" href="#A-device-agnostic-Led-interface-🟢">§</a></h2><p>So let’s say we want to create a device-agnostic (non-blocking) interface for an LED connected to your micro-controller. Here’s how we might do this:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> core::&#123;fmt::<span class="built_in">Debug</span>, task::Poll&#125;;</span><br><span class="line"><span class="keyword">use</span> embedded_hal::digital::v2::&#123;OutputPin, PinState&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedAction</span> &#123;</span><br><span class="line">    Set &#123; is_on: <span class="type">bool</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedError</span>&lt;PinError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">PinSet</span>(PinError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Led</span>&lt;P&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">&#123;</span><br><span class="line">    pin: P,</span><br><span class="line">    is_on: <span class="type">bool</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;P&gt; Led&lt;P&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">    P::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(pin: P) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123; pin, is_on: <span class="literal">false</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, action: &amp;LedAction) &#123;</span><br><span class="line">        <span class="keyword">match</span> action &#123;</span><br><span class="line">            LedAction::Set &#123; is_on &#125; =&gt; <span class="keyword">self</span>.is_on = *is_on,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), LedError&lt;P::Error&gt;&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.pin</span><br><span class="line">            .<span class="title function_ invoke__">set_state</span>(PinState::<span class="title function_ invoke__">from</span>(<span class="keyword">self</span>.is_on))</span><br><span class="line">            .<span class="title function_ invoke__">map_err</span>(LedError::PinSet)?;</span><br><span class="line"></span><br><span class="line">        Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>For a simple example there’s a lot happening, especially if you are new to Rust!</p><ul><li><code>LedAction</code> is an enum we will use to tell the LED how to update.</li><li><code>LedError</code> is an object we will use to represent any error.<ul><li>This receives one generic type, <code>PinError</code> (which implements the <code>Debug</code> trait), since we don’t know the specific type of error a hardware pin might return.</li><li>This also uses the <code>#[derive(...)]</code> macro to automatically derive the traits <code>Copy</code>, <code>Clone</code>, and <code>Debug</code>. Note: We can only derive these traits if all the types within this object also implement the trait. This is why we must explicitly say that <code>PinError</code> must implement the <code>Debug</code> trait.</li></ul></li><li><code>Led</code> is a struct we will use as our LED abstraction, like a class in other languages.<ul><li>For the methods,<ul><li>The <code>new</code> method is our constructor for creating a new LED.</li><li>The <code>run</code> method receives our action telling the LED how to update, and update our LED abstraction’s internal state (but not yet updating the external hardware).<ul><li>This method returns nothing (which is by default the empty tuple <code>()</code>).</li></ul></li><li>The <code>poll</code> method will update the external hardware as needed to match the internal state.<ul><li>This method returns a <code>Poll</code> (which can be either <code>Pending</code> or <code>Ready(value)</code>) of a <code>Result</code> (which can be either <code>Ok(value)</code> or <code>Err(error)</code>) of either a empty value <code>()</code> or an error <code>P::Error</code> (the associated type <code>Error</code>, attached to the <code>OutputPin</code> trait).</li></ul></li></ul></li><li>For the types, this receives one generic type <code>P</code>, which implements <code>OutputPin</code> (provided by the <code>embedded-hal</code> library). We also specify that <code>P::Error</code> (the associated type <code>Error</code>, attached to the <code>OutputPin</code> trait) implements <code>Debug</code>.</li></ul></li></ul><h3 id="An-dummy-GpioA-struct-to-impl-OutputPin-👤">An dummy <code>GpioA</code> struct to <code>impl OutputPin</code> 👤<a class="header-anchor" href="#An-dummy-GpioA-struct-to-impl-OutputPin-👤">§</a></h3><p>Now if you’re curious, here’s what a dummy struct that implements the <code>OutputPin</code> trait would look like.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> embedded_hal::digital::v2::&#123;OutputPin, PinState&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">GpioA</span> &#123;</span><br><span class="line">    state: PinState,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">GpioAError</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">GpioA</span> &#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>() <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            state: PinState::Low,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> <span class="title class_">OutputPin</span> <span class="keyword">for</span> <span class="title class_">GpioA</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = GpioAError;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">set_low</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.state = PinState::Low;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">set_high</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.state = PinState::High;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">set_state</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, state: PinState) <span class="punctuation">-&gt;</span> <span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.state = state;</span><br><span class="line">        <span class="title function_ invoke__">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Note: This won’t do anything!</p></blockquote><p>In the real-world, these structs affect registers on the hardware and are provided by your device’s <code>xxx-hal</code> library, almost certainly generated with a macro.</p><h3 id="An-example-top-level-entry-🔝">An example top-level entry 🔝<a class="header-anchor" href="#An-example-top-level-entry-🔝">§</a></h3><p>To set the stage, let’s show how we might call our <code>Led</code>.</p><p>An example <code>fn main()</code> you can run on a normal PC, using our dummy <code>GpioA</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">led_pin</span> = GpioA::<span class="title function_ invoke__">new</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">led</span> = Led::<span class="title function_ invoke__">new</span>(led_pin);</span><br><span class="line"></span><br><span class="line">    led.<span class="title function_ invoke__">run</span>(&amp;LedAction::Set &#123; is_on: <span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">match</span> led.<span class="title function_ invoke__">poll</span>() &#123;</span><br><span class="line">            Poll::Pending =&gt; <span class="keyword">continue</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; <span class="keyword">break</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                <span class="built_in">println!</span>(<span class="string">&quot;Error: &#123;:?&#125;&quot;</span>, err);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>An example embedded entry for a <a href="https://nz.element14.com/stmicroelectronics/nucleo-f767zi/dev-board-nucleo-32-mcu/dp/2546569">Nucleo-F767ZI</a>:</p><p>(Assuming <a href="https://github.com/knurling-rs/app-template"><code>knurling-rs/app-template</code></a> as a starting point.)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> my_app <span class="keyword">as</span> _; <span class="comment">// global logger + panicking-behavior + memory layout</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> stm32f7xx_hal::&#123;pac, prelude::*&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[cortex_m_rt::entry]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = pac::Peripherals::<span class="title function_ invoke__">take</span>().<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">gpiob</span> = p.GPIOB.<span class="title function_ invoke__">split</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">led_pin</span> = gpiob.pb0.<span class="title function_ invoke__">into_push_pull_output</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">led</span> = Led::<span class="title function_ invoke__">new</span>(led_pin);</span><br><span class="line"></span><br><span class="line">    led.<span class="title function_ invoke__">run</span>(&amp;LedAction::Set &#123; is_on: <span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">match</span> led.<span class="title function_ invoke__">poll</span>() &#123;</span><br><span class="line">            Poll::Pending =&gt; <span class="keyword">continue</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; <span class="keyword">break</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                defmt::<span class="built_in">println!</span>(<span class="string">&quot;Error: &#123;&#125;&quot;</span>, defmt::<span class="title function_ invoke__">Debug2Format</span>(&amp;err));</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="A-more-detailed-Led-using-a-timer-⏲️">A more detailed <code>Led</code> using a timer ⏲️<a class="header-anchor" href="#A-more-detailed-Led-using-a-timer-⏲️">§</a></h3><p>If you’re wondering why there’s a difference between <code>run</code> and <code>poll</code>, let’s change our LED abstraction so we can also tell an LED to blink for a specific amount of time. Since we can’t do a blocking <code>sleep</code>, you’ll see why <code>poll</code> is designed to be non-blocking.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> core::fmt::<span class="built_in">Debug</span>;</span><br><span class="line"><span class="keyword">use</span> core::task::Poll;</span><br><span class="line"><span class="keyword">use</span> embedded_hal::digital::v2::&#123;OutputPin, PinState&#125;;</span><br><span class="line"><span class="keyword">use</span> fugit::TimerDurationU32 <span class="keyword">as</span> TimerDuration;</span><br><span class="line"><span class="keyword">use</span> fugit_timer::Timer;</span><br><span class="line"><span class="keyword">use</span> nb;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Clone, Copy, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedAction</span>&lt;<span class="keyword">const</span> TIMER_HZ: <span class="type">u32</span>&gt; &#123;</span><br><span class="line">    Set &#123; is_on: <span class="type">bool</span> &#125;,</span><br><span class="line">    Blink &#123; duration: TimerDuration&lt;TIMER_HZ&gt; &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Clone, Copy, Debug, Format)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedBlinkStatus</span> &#123;</span><br><span class="line">    Start,</span><br><span class="line">    Wait,</span><br><span class="line">    Done,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Clone, Copy, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedState</span>&lt;<span class="keyword">const</span> TIMER_HZ: <span class="type">u32</span>&gt; &#123;</span><br><span class="line">    Set &#123;</span><br><span class="line">        is_on: <span class="type">bool</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    Blink &#123;</span><br><span class="line">        status: LedBlinkStatus,</span><br><span class="line">        duration: TimerDuration&lt;TIMER_HZ&gt;,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Clone, Copy, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Led</span>&lt;P, <span class="keyword">const</span> TIMER_HZ: <span class="type">u32</span>, T&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">    T: Timer&lt;TIMER_HZ&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    pin: P,</span><br><span class="line">    timer: T,</span><br><span class="line">    state: <span class="type">Option</span>&lt;LedState&lt;TIMER_HZ&gt;&gt;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Clone, Copy, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedError</span>&lt;PinError: <span class="built_in">Debug</span>, TimerError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">PinSet</span>(PinError),</span><br><span class="line">    <span class="title function_ invoke__">TimerStart</span>(TimerError),</span><br><span class="line">    <span class="title function_ invoke__">TimerWait</span>(TimerError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;P, <span class="keyword">const</span> TIMER_HZ: <span class="type">u32</span>, T&gt; Led&lt;P, TIMER_HZ, T&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">    P::Error: <span class="built_in">Debug</span>,</span><br><span class="line">    T: Timer&lt;TIMER_HZ&gt;,</span><br><span class="line">    T::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(pin: P, timer: T) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            pin,</span><br><span class="line">            timer,</span><br><span class="line">            state: <span class="literal">None</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, action: &amp;LedAction) &#123;</span><br><span class="line">        <span class="keyword">match</span> action &#123;</span><br><span class="line">            LedAction::Set &#123; is_on &#125; =&gt; &#123;</span><br><span class="line">                <span class="keyword">self</span>.state = <span class="title function_ invoke__">Some</span>(LedState::Set &#123; is_on: *is_on &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">            LedAction::Blink &#123; duration &#125; =&gt; &#123;</span><br><span class="line">                <span class="keyword">self</span>.state = <span class="title function_ invoke__">Some</span>(LedState::Blink &#123;</span><br><span class="line">                    status: LedBlinkStatus::Start,</span><br><span class="line">                    duration: *duration,</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), LedError&lt;P::Error, T::Error&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">match</span> <span class="keyword">self</span>.state &#123;</span><br><span class="line">            <span class="literal">None</span> =&gt; Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())),</span><br><span class="line">            <span class="title function_ invoke__">Some</span>(LedState::Set &#123; is_on &#125;) =&gt; &#123;</span><br><span class="line">                <span class="comment">// set led state</span></span><br><span class="line">                <span class="keyword">self</span>.pin</span><br><span class="line">                    .<span class="title function_ invoke__">set_state</span>(PinState::<span class="title function_ invoke__">from</span>(is_on))</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(LedError::PinSet)?;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">self</span>.state = <span class="literal">None</span>;</span><br><span class="line"></span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="title function_ invoke__">Some</span>(LedState::Blink &#123; duration, status &#125;) =&gt; &#123;</span><br><span class="line">                <span class="keyword">match</span> status &#123;</span><br><span class="line">                    LedBlinkStatus::Start =&gt; &#123;</span><br><span class="line">                        <span class="comment">// start timer</span></span><br><span class="line">                        <span class="keyword">self</span>.timer.<span class="title function_ invoke__">start</span>(duration).<span class="title function_ invoke__">map_err</span>(LedError::TimerStart)?;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// turn led on</span></span><br><span class="line">                        <span class="keyword">self</span>.pin.<span class="title function_ invoke__">set_high</span>().<span class="title function_ invoke__">map_err</span>(LedError::PinSet)?;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// update state</span></span><br><span class="line">                        <span class="keyword">self</span>.state = <span class="title function_ invoke__">Some</span>(LedState::Blink &#123;</span><br><span class="line">                            status: LedBlinkStatus::Wait,</span><br><span class="line">                            duration,</span><br><span class="line">                        &#125;);</span><br><span class="line"></span><br><span class="line">                        Poll::Pending</span><br><span class="line">                    &#125;</span><br><span class="line">                    LedBlinkStatus::Wait =&gt; <span class="keyword">match</span> <span class="keyword">self</span>.timer.<span class="title function_ invoke__">wait</span>() &#123;</span><br><span class="line">                        <span class="title function_ invoke__">Err</span>(nb::Error::<span class="title function_ invoke__">Other</span>(err)) =&gt; Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(LedError::<span class="title function_ invoke__">TimerWait</span>(err))),</span><br><span class="line">                        <span class="title function_ invoke__">Err</span>(nb::Error::WouldBlock) =&gt; Poll::Pending,</span><br><span class="line">                        <span class="title function_ invoke__">Ok</span>(()) =&gt; &#123;</span><br><span class="line">                            <span class="keyword">self</span>.state = <span class="title function_ invoke__">Some</span>(LedState::Blink &#123;</span><br><span class="line">                                status: LedBlinkStatus::Done,</span><br><span class="line">                                duration,</span><br><span class="line">                            &#125;);</span><br><span class="line"></span><br><span class="line">                            Poll::Pending</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;,</span><br><span class="line">                    LedBlinkStatus::Done =&gt; &#123;</span><br><span class="line">                        <span class="keyword">self</span>.pin.<span class="title function_ invoke__">set_low</span>().<span class="title function_ invoke__">map_err</span>(LedError::PinSet)?;</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">self</span>.state = <span class="literal">None</span>;</span><br><span class="line"></span><br><span class="line">                        Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Oh gosh that’s a mouthful!</p><ul><li><code>LedState</code> is an enum with either:<ul><li><code>Set</code> with a boolean <code>is_on</code></li><li><code>Blink</code> with a <code>status</code> (<code>LedBlinkStatus</code>) and a <code>duration</code><ul><li><code>LedBlinkStatus</code> is either <code>Start</code>, <code>Wait</code>, or <code>Done</code>.</li></ul></li></ul></li><li>In <code>Led</code>, we’re using a state machine:<ul><li>The current <code>LedState</code> is stored in <code>self.state</code>.</li><li>For the methods:<ul><li>On <code>run</code>, we receive an <code>LedAction</code> and update <code>self.state</code>, but don’t do anything to affect the LED pin or timer.</li><li>On <code>poll</code>,<ul><li>If the current state is <code>None</code>, we’re done and we return (send a message to whoever polled us) that we’re done (<code>Poll::Ready(Ok(()))</code>)</li><li>If the current state is <code>Some(LedState::Set &#123;&#125;)</code>, we set the LED to the desired on/off state. Then, we reset the state to <code>None</code> and return that we’re done (<code>Poll::Ready(Ok(()))</code>).</li><li>If the current state is <code>Some(LedState::Blink &#123;&#125;)</code>, the method handles the blinking action based on the current <code>LedBlinkStatus</code>:<ul><li>If the status is <code>Start</code>, it turns the LED on, starts the timer with the given duration, updates the status to <code>Wait</code>, and returns <code>Poll::Pending</code>, indicating it’s still waiting.</li><li>If the status is <code>Wait</code>, it checks the timer:<ul><li>If the timer returns an error, it returns <code>Poll::Ready(Err(LedError::TimerWait(err)))</code>, propagating the error.</li><li>If the timer is not done and returns “would block”, it returns <code>Poll::Pending</code>, indicating it’s still waiting.</li><li>If the timer is done, it updates the status to <code>Done</code> and returns <code>Poll::Pending</code>.</li></ul></li><li>If the status is <code>Done</code>, it turns the LED off, resets the state to <code>None</code>, and returns <code>Poll::Ready(Ok(()))</code>, indicating it’s done.</li></ul></li><li>In any of the steps mentioned above, if we get an erro, we send the error upwards <code>return Poll::Ready(Err(LedError))</code>.</li></ul></li></ul></li><li>For the types, this receives three generics:<ul><li>A type <code>P</code>, which implements <code>OutputPin</code> (provided by the <code>embedded-hal</code> library). We also specify that <code>P::Error</code> (the associated type <code>Error</code>, attached to the <code>OutputPin</code> trait) implements <code>Debug</code>.</li><li>A constant <code>TIMER_HZ</code>, which describes the frequency of the timer.</li><li>A type <code>T</code>, which implements <code>Timer&lt;TIMER_HZ&gt;</code> (provided by the <code>fugit-timer</code> library). We also specify that <code>T::Error</code> implements <code>Debug</code>.</li></ul></li></ul></li></ul><p>I hope that makes some sense.</p><p>And for completion, here’s an example embedded entry for a <a href="https://nz.element14.com/stmicroelectronics/nucleo-f767zi/dev-board-nucleo-32-mcu/dp/2546569">Nucleo-F767ZI</a>:</p><p>(Assuming <a href="https://github.com/knurling-rs/app-template"><code>knurling-rs/app-template</code></a> as a starting point.)</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#![no_main]</span></span><br><span class="line"><span class="meta">#![no_std]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> my_app <span class="keyword">as</span> _; <span class="comment">// global logger + panicking-behavior + memory layout</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> stm32f7xx_hal::&#123;pac, prelude::*&#125;;</span><br><span class="line"><span class="keyword">use</span> fugit::ExtU32;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[cortex_m_rt::entry]</span></span><br><span class="line"><span class="keyword">fn</span> <span class="title function_">main</span>() <span class="punctuation">-&gt;</span> ! &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">p</span> = pac::Peripherals::<span class="title function_ invoke__">take</span>().<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">rcc</span> = p.RCC.<span class="title function_ invoke__">constrain</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">clocks</span> = rcc.cfgr.<span class="title function_ invoke__">sysclk</span>(<span class="number">216</span>.<span class="title function_ invoke__">MHz</span>()).<span class="title function_ invoke__">freeze</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">gpiob</span> = p.GPIOB.<span class="title function_ invoke__">split</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> <span class="variable">led_pin</span> = gpiob.pb0.<span class="title function_ invoke__">into_push_pull_output</span>();</span><br><span class="line">    <span class="keyword">let</span> <span class="variable">led_timer</span> = p.TIM5.<span class="title function_ invoke__">counter_us</span>(&amp;clocks);</span><br><span class="line">    <span class="keyword">let</span> <span class="keyword">mut </span><span class="variable">led</span> = Led::<span class="title function_ invoke__">new</span>(led_pin, led_timer);</span><br><span class="line"></span><br><span class="line">    led.<span class="title function_ invoke__">run</span>(&amp;LedAction::Blink &#123; duration: <span class="number">200</span>.<span class="title function_ invoke__">millis</span>() &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">loop</span> &#123;</span><br><span class="line">        <span class="keyword">match</span> led.<span class="title function_ invoke__">poll</span>() &#123;</span><br><span class="line">            Poll::Pending =&gt; <span class="keyword">continue</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; <span class="keyword">break</span>,</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                defmt::<span class="built_in">println!</span>(<span class="string">&quot;Error: &#123;&#125;&quot;</span>, defmt::<span class="title function_ invoke__">Debug2Format</span>(&amp;err));</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>For the rest of the post, I’ll be assuming the original, simpler <code>Led</code> struct.</p><h2 id="A-Runner-to-control-multiple-Led-🚥">A <code>Runner</code> to control multiple <code>Led</code> 🚥<a class="header-anchor" href="#A-Runner-to-control-multiple-Led-🚥">§</a></h2><p>Now say we want to control multiple LEDs together.</p><p>We will create a <code>Runner</code> that can receive a command, delegate that command to the associated LED, and poll all active commands until completion.</p><p>To start, we know there will be three LEDs: a green LED, a blue LED, and a red LED.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> alloc::collections::VecDeque;</span><br><span class="line"><span class="keyword">use</span> core::fmt::<span class="built_in">Debug</span>;</span><br><span class="line"><span class="keyword">use</span> core::task::Poll;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">Command</span> &#123;</span><br><span class="line">    <span class="title function_ invoke__">GreenLed</span>(LedAction),</span><br><span class="line">    <span class="title function_ invoke__">BlueLed</span>(LedAction),</span><br><span class="line">    <span class="title function_ invoke__">RedLed</span>(LedAction),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">CommandError</span>&lt;GreenLedError: <span class="built_in">Debug</span>, BlueLedError: <span class="built_in">Debug</span>, RedLedError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">GreenLedError</span>(GreenLedError),</span><br><span class="line">    <span class="title function_ invoke__">BlueLedError</span>(BlueLedError),</span><br><span class="line">    <span class="title function_ invoke__">RedLedError</span>(RedLedError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;GreenPin, BluePin, RedPin&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenPin: OutputPin,</span><br><span class="line">    BluePin: OutputPin,</span><br><span class="line">    RedPin: OutputPin,</span><br><span class="line">&#123;</span><br><span class="line">    active_commands: VecDeque&lt;Command&gt;,</span><br><span class="line">    green_led: Led&lt;GreenPin&gt;,</span><br><span class="line">    blue_led: Led&lt;BluePin&gt;,</span><br><span class="line">    red_led: Led&lt;RedPin&gt;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;GreenPin, BluePin, RedPin&gt; Runner&lt;GreenPin, BluePin, RedPin&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenPin: OutputPin,</span><br><span class="line">    GreenPin::Error: <span class="built_in">Debug</span>,</span><br><span class="line">    BluePin: OutputPin,</span><br><span class="line">    BluePin::Error: <span class="built_in">Debug</span>,</span><br><span class="line">    RedPin: OutputPin,</span><br><span class="line">    RedPin::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(green_led: GreenLed, blue_led: BlueLed, red_led: RedLed) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            active_commands: VecDeque::<span class="title function_ invoke__">new</span>(),</span><br><span class="line">            green_led,</span><br><span class="line">            blue_led,</span><br><span class="line">            red_led,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, command: Command) &#123;</span><br><span class="line">        <span class="keyword">match</span> command &#123;</span><br><span class="line">            Command::<span class="title function_ invoke__">GreenLed</span>(action) =&gt; <span class="keyword">self</span>.green_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">            Command::<span class="title function_ invoke__">BlueLed</span>(action) =&gt; <span class="keyword">self</span>.blue_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">            Command::<span class="title function_ invoke__">RedLed</span>(action) =&gt; <span class="keyword">self</span>.red_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">poll</span>(</span><br><span class="line">        &amp;<span class="keyword">mut</span> <span class="keyword">self</span>,</span><br><span class="line">    ) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), CommandError&lt;GreenPin::Error, BluePin::Error, RedPin::Error&gt;&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">num_commands</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>();</span><br><span class="line">        <span class="keyword">for</span> <span class="variable">_command_index</span> <span class="keyword">in</span> <span class="number">0</span>..num_commands &#123;</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">command</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">pop_front</span>().<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">result</span> = <span class="keyword">match</span> command &#123;</span><br><span class="line">                Command::<span class="title function_ invoke__">GreenLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .green_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">GreenLedError</span>(err)),</span><br><span class="line">                Command::<span class="title function_ invoke__">BlueLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .blue_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">BlueLedError</span>(err)),</span><br><span class="line">                Command::<span class="title function_ invoke__">RedLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .red_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">RedLedError</span>(err)),</span><br><span class="line">            &#125;;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">match</span> result &#123;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; &#123;&#125;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">return</span> Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err));</span><br><span class="line">                &#125;</span><br><span class="line">                Poll::Pending =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>() == <span class="number">0</span> &#123;</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            Poll::Pending</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Woah, okay!</p><ul><li><code>Command</code> is an enum to represents any action we might want to send to any LED.</li><li><code>CommandError</code> is an enum to represent any error that might happen with any LED.</li><li><code>Runner</code> is the struct to manage our three LEDs (green, blue, and red).<ul><li>We store a list of active commands (<code>active_commands</code>) and an <code>Led</code> object for each color.</li><li>For the methods:<ul><li><code>new</code>: Creates a new <code>Runner</code> object, taking green, blue, and red LEDs as inputs.</li><li><code>run</code>: Takes a <code>Command</code> as input, performs the action for the specified LED color, and adds the command to the list of active commands.</li><li><code>poll</code>: Checks the progress of each command in the list:<ul><li>If a command is done, it removes the command from the list.</li><li>If a command is not done, it keeps the command in the list.</li><li>If there’s an error, it returns the error and puts the command back in the list.</li></ul></li></ul></li></ul></li></ul><blockquote><p>Note: In this code I’m using <a href="https://doc.rust-lang.org/alloc/collections/vec_deque/struct.VecDeque.html"><code>alloc::collections::VecDeque</code></a>, to make things easier. If we want collections without using <code>alloc</code>, I recommend the crate <a href="https://github.com/japaric/heapless"><code>heapless</code></a>, so here we’d use <a href="https://docs.rs/heapless/latest/heapless/struct.Deque.html"><code>heapless::Deque</code></a>.</p></blockquote><p>In a nutshell, this code manages a set of LEDs, allowing you to turn them on or off by adding commands to a list, and it checks the progress of these commands. If you run into any issues, it will handle the errors for each LED color.</p><h2 id="Problem-1-Generic-type-hell-😈">Problem #1: Generic type hell 😈<a class="header-anchor" href="#Problem-1-Generic-type-hell-😈">§</a></h2><p>Now here’s the point where I can finally start to explain why I had such a hard time to make the code for my robot re-usable as a library.</p><p>Let’s go back to <code>Runner</code>’s generic types: <code>GreenPin</code>, <code>BluePin</code>, and <code>RedPin</code>.</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;GreenPin, BluePin, RedPin&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">  GreenPin: OutputPin,</span><br><span class="line">  BluePin: OutputPin,</span><br><span class="line">  RedPin: OutputPin,</span><br><span class="line">&#123;</span><br><span class="line">    active_commands: VecDeque&lt;Command&gt;,</span><br><span class="line">    green_led: Led&lt;GreenPin&gt;,</span><br><span class="line">    blue_led: Led&lt;BluePin&gt;,</span><br><span class="line">    red_led: Led&lt;RedPin&gt;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now, this looks okay (when you’ve become accustomed to generic types), but this is only considering our original <code>Led</code> struct that only needs a <code>P: OutputPin</code>. In our later <code>Led</code> struct, we need two more generics: <code>const TIMER_HZ: u32</code> and <code>T: Timer&lt;TIMER_HZ&gt;</code>.</p><p>Imagine we needed more:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;</span><br><span class="line">    GreenLedPin,</span><br><span class="line">    GreenLedTimer,</span><br><span class="line">    <span class="keyword">const</span> GREEN_LED_TIMER_HZ: <span class="type">u32</span>,</span><br><span class="line">    BlueLedPin,</span><br><span class="line">    BlueLedTimer,</span><br><span class="line">    <span class="keyword">const</span> BLUE_LED_TIMER_HZ: <span class="type">u32</span>,</span><br><span class="line">    RedLedPin,</span><br><span class="line">    RedLedTimer,</span><br><span class="line">    <span class="keyword">const</span> RED_LED_TIMER_HZ: <span class="type">u32</span>,</span><br><span class="line">    XAxisStepperDriver,</span><br><span class="line">    XAxisTimer,</span><br><span class="line">    <span class="keyword">const</span> X_AXIS_TIMER_HZ: <span class="type">u32</span>,</span><br><span class="line">    YAxisStepperDriver,</span><br><span class="line">    YAxisTimer,</span><br><span class="line">    <span class="keyword">const</span> Y_AXIS_TIMER_HZ: <span class="type">u32</span>,</span><br><span class="line">    MainSpindleDriver,</span><br><span class="line">&gt; <span class="keyword">where</span></span><br><span class="line">    GreenLedPin: OutputPin,</span><br><span class="line">    GreenLedTimer: Timer&lt;GREEN_LED_TIMER_HZ&gt;,</span><br><span class="line">    BlueLedPin: OutputPin,</span><br><span class="line">    BlueLedTimer: Timer&lt;BLUE_LED_TIMER_HZ&gt;,</span><br><span class="line">    RedLedPin: OutputPin,</span><br><span class="line">    RedLedTimer: Timer&lt;RED_LED_TIMER_HZ&gt;,</span><br><span class="line">    XAxisStepperDriver: SetDirection + Step,</span><br><span class="line">    XAxisTimer: Timer&lt;X_AXIS_TIMER_HZ&gt;,</span><br><span class="line">    YAxisStepperDriver: SetDirection + Step,</span><br><span class="line">    YAxisTimer: Timer&lt;Y_AXIS_TIMER_HZ&gt;,</span><br><span class="line">    MainSpindleDriver: SpindleDriver</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> green_led: Led&lt;GreenLedPin, GreenLedTimer, TICK_TIMER_HZ&gt;,</span><br><span class="line">    <span class="keyword">pub</span> blue_led: Led&lt;BlueLedPin, BlueLedTimer, TICK_TIMER_HZ&gt;,</span><br><span class="line">    <span class="keyword">pub</span> red_led: Led&lt;RedLedPin, RedLedTimer, TICK_TIMER_HZ&gt;,</span><br><span class="line">    <span class="keyword">pub</span> x_axis: Axis&lt;AxisMotionControl&lt;XAxisDriver, XAxisTimer, X_AXIS_TIMER_HZ&gt;&gt;,</span><br><span class="line">    <span class="keyword">pub</span> y_axis: Axis&lt;AxisMotionControl&lt;YAxisDriver, YAxisTimer, Y_AXIS_TIMER_HZ&gt;&gt;,</span><br><span class="line">    <span class="keyword">pub</span> main_spindle: Spindle&lt;MainSpindleDriver&gt;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Yuck. 🤮</p><p>The problem with these generic types is that anything that consumes a struct also need to provide their generic types. This becomes a sort of “generic hell”, where we can’t escape generic types just bubbling up to the consumer’s code and beyond.</p><p>The solution to generic type hell is traits.</p><p>So let’s go!</p><h2 id="Solution-1-Traits-to-the-rescue-😇">Solution #1: Traits to the rescue 😇<a class="header-anchor" href="#Solution-1-Traits-to-the-rescue-😇">§</a></h2><p>Beyond the generic type hell problem, we want our system to support multiple types of hardware interfaces that affect the real world.</p><h3 id="trait-Actuator-💪"><code>trait Actuator</code> 💪<a class="header-anchor" href="#trait-Actuator-💪">§</a></h3><p>We create an <code>Actuator</code> trait to generalize our use of hardware interfaces (while still being device agnostic).</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> core::task::Poll;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">trait</span> <span class="title class_">Actuator</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Action</span>;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span>: <span class="built_in">Debug</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, action: &amp;<span class="keyword">Self</span>::Action);</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>As an aside, a Rust expert might say that <code>run</code> should return a <code>Future</code>, which we can <code>poll</code>. I chose this design because such a future would need a mutable reference to the hardware peripherals (and Rust has strict rules about object ownership, references, and mutability) and at the time I wanted to avoid using allocations (<code>Rc</code>). If this is an achievable change for my current code base, I’d love to learn how, every day is a school day.</p></blockquote><h3 id="impl-Actuator-for-Led-🟢"><code>impl Actuator for Led</code> 🟢<a class="header-anchor" href="#impl-Actuator-for-Led-🟢">§</a></h3><p>Now to update <code>Led</code>:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> core::&#123;fmt::<span class="built_in">Debug</span>, task::Poll&#125;;</span><br><span class="line"><span class="keyword">use</span> embedded_hal::digital::v2::&#123;OutputPin, PinState&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedAction</span> &#123;</span><br><span class="line">    Set &#123; is_on: <span class="type">bool</span> &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedError</span>&lt;PinError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">PinSet</span>(PinError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Led</span>&lt;P&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">&#123;</span><br><span class="line">    pin: P,</span><br><span class="line">    is_on: <span class="type">bool</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;P&gt; Led&lt;P&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(pin: P) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123; pin, is_on: <span class="literal">false</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;P&gt; Actuator <span class="keyword">for</span> <span class="title class_">Led</span>&lt;P&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    P: OutputPin,</span><br><span class="line">    P::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Action</span> = LedAction;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = LedError&lt;P::Error&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, action: &amp;<span class="keyword">Self</span>::Action) &#123;</span><br><span class="line">        <span class="keyword">match</span> action &#123;</span><br><span class="line">            LedAction::Set &#123; is_on &#125; =&gt; <span class="keyword">self</span>.is_on = *is_on,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.pin</span><br><span class="line">            .<span class="title function_ invoke__">set_state</span>(PinState::<span class="title function_ invoke__">from</span>(<span class="keyword">self</span>.is_on))</span><br><span class="line">            .<span class="title function_ invoke__">map_err</span>(LedError::PinSet)?;</span><br><span class="line"></span><br><span class="line">        Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The code is similar, except now we’re implementing the <code>Actuator</code> trait instead of implementing those methods directly on the struct.</p><h3 id="Same-Runner-different-generics-🚥">Same <code>Runner</code>, different generics 🚥<a class="header-anchor" href="#Same-Runner-different-generics-🚥">§</a></h3><p>And now we can improve the <code>Runner</code> struct:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    active_commands: VecDeque&lt;Command&gt;,</span><br><span class="line">    green_led: GreenLed,</span><br><span class="line">    blue_led: BlueLed,</span><br><span class="line">    red_led: RedLed,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>By using a trait, we’re able to hide the generics of the implementing struct and instead focus our generics on the structs we need.</p><p>We are saying: <code>Runner</code> will receive three types (named <code>GreenLed</code>, <code>BlueLed</code>, and <code>RedLed</code>), where each type must implement the <code>Actuator</code> trait where the associated type <code>Action</code> is <code>LedAction</code>. Those three types correspond to a struct, and in this case correspond to the <code>Led</code> struct, but we didn’t specify so as to be generic.</p><p>Now the definition of <code>Runner</code> doesn’t “leak” the generic types of <code>Led</code>.</p><p>For completeness:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> alloc::collections::VecDeque;</span><br><span class="line"><span class="keyword">use</span> core::fmt::<span class="built_in">Debug</span>;</span><br><span class="line"><span class="keyword">use</span> core::task::Poll;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">Command</span> &#123;</span><br><span class="line">    <span class="title function_ invoke__">GreenLed</span>(LedAction),</span><br><span class="line">    <span class="title function_ invoke__">BlueLed</span>(LedAction),</span><br><span class="line">    <span class="title function_ invoke__">RedLed</span>(LedAction),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">CommandError</span>&lt;GreenLedError: <span class="built_in">Debug</span>, BlueLedError: <span class="built_in">Debug</span>, RedLedError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">GreenLedError</span>(GreenLedError),</span><br><span class="line">    <span class="title function_ invoke__">BlueLedError</span>(BlueLedError),</span><br><span class="line">    <span class="title function_ invoke__">RedLedError</span>(RedLedError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    active_commands: VecDeque&lt;Command&gt;,</span><br><span class="line">    green_led: GreenLed,</span><br><span class="line">    blue_led: BlueLed,</span><br><span class="line">    red_led: RedLed,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;GreenLed, BlueLed, RedLed&gt; Runner&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    GreenLed::Error: <span class="built_in">Debug</span>,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed::Error: <span class="built_in">Debug</span>,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(green_led: GreenLed, blue_led: BlueLed, red_led: RedLed) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            active_commands: VecDeque::<span class="title function_ invoke__">new</span>(),</span><br><span class="line">            green_led,</span><br><span class="line">            blue_led,</span><br><span class="line">            red_led,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, command: Command) &#123;</span><br><span class="line">        <span class="keyword">match</span> command &#123;</span><br><span class="line">            Command::<span class="title function_ invoke__">GreenLed</span>(action) =&gt; <span class="keyword">self</span>.green_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">            Command::<span class="title function_ invoke__">BlueLed</span>(action) =&gt; <span class="keyword">self</span>.blue_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">            Command::<span class="title function_ invoke__">RedLed</span>(action) =&gt; <span class="keyword">self</span>.red_led.<span class="title function_ invoke__">run</span>(&amp;action),</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">poll</span>(</span><br><span class="line">        &amp;<span class="keyword">mut</span> <span class="keyword">self</span>,</span><br><span class="line">    ) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), CommandError&lt;GreenLed::Error, BlueLed::Error, RedLed::Error&gt;&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">num_commands</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>();</span><br><span class="line">        <span class="keyword">for</span> <span class="variable">_command_index</span> <span class="keyword">in</span> <span class="number">0</span>..num_commands &#123;</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">command</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">pop_front</span>().<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">result</span> = <span class="keyword">match</span> command &#123;</span><br><span class="line">                Command::<span class="title function_ invoke__">GreenLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .green_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">GreenLedError</span>(err)),</span><br><span class="line">                Command::<span class="title function_ invoke__">BlueLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .blue_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">BlueLedError</span>(err)),</span><br><span class="line">                Command::<span class="title function_ invoke__">RedLed</span>(_) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .red_led</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">RedLedError</span>(err)),</span><br><span class="line">            &#125;;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">match</span> result &#123;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; &#123;&#125;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">return</span> Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err));</span><br><span class="line">                &#125;</span><br><span class="line">                Poll::Pending =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>() == <span class="number">0</span> &#123;</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            Poll::Pending</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However we still have the problem where the shape of 3 LEDs: green, blue, and red, is pre-defined.</p><blockquote><p>Note: If you’re wondering why we need to be so specific with our <code>Command</code> enum, our <code>run</code> match, and our <code>poll</code> match: it’s because we’re avoiding heap allocations and dynamic objects. We can only use static types, so Rust knows the size of every struct at compile time, and objects are created on the stack. This approach, while extra boilerplate-y, is very efficient.</p></blockquote><h2 id="Problem-2-Pre-defined-shapes-😈">Problem #2: Pre-defined shapes 😈<a class="header-anchor" href="#Problem-2-Pre-defined-shapes-😈">§</a></h2><p>If you want to use my <code>Runner</code> as written above, sure the use of generic types meant you can use any device, and now the use of traits means generic types don’t leak upward. But you better have only 3 LEDs, and you better want them to be named “Green”, “Blue”, and “Red”.</p><p>Generic types were helpful to be device-agnostic, but at the same time didn’t help us be shape-agnostic.</p><p>To solve this, I discovered another trick, while still avoiding <code>alloc</code>.</p><h2 id="Solution-2-User-defined-shapes-😇">Solution #2: User defined shapes 😇<a class="header-anchor" href="#Solution-2-User-defined-shapes-😇">§</a></h2><p>What if, in all the places that we hard-code <code>GreenLed</code>, <code>BlueLed</code>, and <code>RedLed</code>, we could allow the user to give us something which did those things?</p><p>Let’s make a new trait for this:</p><h3 id="trait-ActuatorSet-⛶⛶⛶"><code>trait ActuatorSet</code> ⛶⛶⛶<a class="header-anchor" href="#trait-ActuatorSet-⛶⛶⛶">§</a></h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="keyword">trait</span> <span class="title class_">ActuatorSet</span> &#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Action</span>: <span class="built_in">Copy</span> + <span class="built_in">Clone</span> + <span class="built_in">Debug</span>;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Id</span>: <span class="built_in">Copy</span> + <span class="built_in">Clone</span> + <span class="built_in">Debug</span>;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span>: <span class="built_in">Copy</span> + <span class="built_in">Clone</span> + <span class="built_in">Debug</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, id: &amp;<span class="keyword">Self</span>::Id, action: &amp;<span class="keyword">Self</span>::Action);</span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, id: &amp;<span class="keyword">Self</span>::Id) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Here we design a way to store a set of actuators for a single type (e.g. LED).</p><p>And then the user can defined their own <code>ActuatorSet</code> as follows:</p><h3 id="impl-LedSet-for-ActuatorSet-🚥"><code>impl LedSet for ActuatorSet</code> 🚥<a class="header-anchor" href="#impl-LedSet-for-ActuatorSet-🚥">§</a></h3><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedId</span> &#123;</span><br><span class="line">    Green,</span><br><span class="line">    Blue,</span><br><span class="line">    Red,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">LedSetError</span>&lt;GreenLedError: <span class="built_in">Debug</span>, BlueLedError: <span class="built_in">Debug</span>, RedLedError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">GreenLedError</span>(GreenLedError),</span><br><span class="line">    <span class="title function_ invoke__">BlueLedError</span>(BlueLedError),</span><br><span class="line">    <span class="title function_ invoke__">RedLedError</span>(RedLedError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">LedSet</span>&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    green_led: GreenLed,</span><br><span class="line">    blue_led: BlueLed,</span><br><span class="line">    red_led: RedLed,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;GreenLed, BlueLed, RedLed&gt; LedSet&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(green_led: GreenLed, blue_led: BlueLed, red_led: RedLed) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            green_led,</span><br><span class="line">            blue_led,</span><br><span class="line">            red_led,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;GreenLed, BlueLed, RedLed&gt; ActuatorSet <span class="keyword">for</span> <span class="title class_">LedSet</span>&lt;GreenLed, BlueLed, RedLed&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    GreenLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    GreenLed::Error: <span class="built_in">Copy</span> + <span class="built_in">Debug</span>,</span><br><span class="line">    BlueLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    BlueLed::Error: <span class="built_in">Copy</span> + <span class="built_in">Debug</span>,</span><br><span class="line">    RedLed: Actuator&lt;Action = LedAction&gt;,</span><br><span class="line">    RedLed::Error: <span class="built_in">Copy</span> + <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Action</span> = LedAction;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Id</span> = LedId;</span><br><span class="line">    <span class="keyword">type</span> <span class="title class_">Error</span> = LedSetError&lt;GreenLed::Error, BlueLed::Error, RedLed::Error&gt;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, id: &amp;<span class="keyword">Self</span>::Id, action: &amp;<span class="keyword">Self</span>::Action) &#123;</span><br><span class="line">        <span class="keyword">match</span> id &#123;</span><br><span class="line">            LedId::Green =&gt; <span class="keyword">self</span>.green_led.<span class="title function_ invoke__">run</span>(action),</span><br><span class="line">            LedId::Blue =&gt; <span class="keyword">self</span>.blue_led.<span class="title function_ invoke__">run</span>(action),</span><br><span class="line">            LedId::Red =&gt; <span class="keyword">self</span>.red_led.<span class="title function_ invoke__">run</span>(action),</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, id: &amp;<span class="keyword">Self</span>::Id) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), <span class="keyword">Self</span>::Error&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">match</span> id &#123;</span><br><span class="line">            LedId::Green =&gt; <span class="keyword">self</span></span><br><span class="line">                .green_led</span><br><span class="line">                .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                .<span class="title function_ invoke__">map_err</span>(|err| LedSetError::<span class="title function_ invoke__">GreenLedError</span>(err)),</span><br><span class="line">            LedId::Blue =&gt; <span class="keyword">self</span></span><br><span class="line">                .blue_led</span><br><span class="line">                .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                .<span class="title function_ invoke__">map_err</span>(|err| LedSetError::<span class="title function_ invoke__">BlueLedError</span>(err)),</span><br><span class="line">            LedId::Red =&gt; <span class="keyword">self</span></span><br><span class="line">                .red_led</span><br><span class="line">                .<span class="title function_ invoke__">poll</span>()</span><br><span class="line">                .<span class="title function_ invoke__">map_err</span>(|err| LedSetError::<span class="title function_ invoke__">RedLedError</span>(err)),</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Runner-LedSet-🏃"><code>Runner&lt;LedSet&gt;</code> 🏃<a class="header-anchor" href="#Runner-LedSet-🏃">§</a></h3><p>And now our runner can be re-written to receive this actuator set:</p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> alloc::collections::VecDeque;</span><br><span class="line"><span class="keyword">use</span> core::fmt::<span class="built_in">Debug</span>;</span><br><span class="line"><span class="keyword">use</span> core::task::Poll;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">Command</span>&lt;LedId&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">Led</span>(LedId, LedAction),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#[derive(Copy, Clone, Debug)]</span></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">enum</span> <span class="title class_">CommandError</span>&lt;LedError: <span class="built_in">Debug</span>&gt; &#123;</span><br><span class="line">    <span class="title function_ invoke__">LedError</span>(LedError),</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">pub</span> <span class="keyword">struct</span> <span class="title class_">Runner</span>&lt;LedSet&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    LedSet: ActuatorSet&lt;Action = LedAction&gt;,</span><br><span class="line">&#123;</span><br><span class="line">    active_commands: VecDeque&lt;Command&lt;LedSet::Id&gt;&gt;,</span><br><span class="line">    leds: LedSet,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;LedSet&gt; Runner&lt;LedSet&gt;</span><br><span class="line"><span class="keyword">where</span></span><br><span class="line">    LedSet: ActuatorSet&lt;Action = LedAction&gt;,</span><br><span class="line">    LedSet::Error: <span class="built_in">Debug</span>,</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">new</span>(leds: LedSet) <span class="punctuation">-&gt;</span> <span class="keyword">Self</span> &#123;</span><br><span class="line">        <span class="keyword">Self</span> &#123;</span><br><span class="line">            active_commands: VecDeque::<span class="title function_ invoke__">new</span>(),</span><br><span class="line">            leds,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">run</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, command: Command&lt;LedSet::Id&gt;) &#123;</span><br><span class="line">        <span class="keyword">match</span> command &#123;</span><br><span class="line">            Command::<span class="title function_ invoke__">Led</span>(id, action) =&gt; <span class="keyword">self</span>.leds.<span class="title function_ invoke__">run</span>(&amp;id, &amp;action),</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="title function_">poll</span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) <span class="punctuation">-&gt;</span> Poll&lt;<span class="type">Result</span>&lt;(), CommandError&lt;LedSet::Error&gt;&gt;&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="variable">num_commands</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>();</span><br><span class="line">        <span class="keyword">for</span> <span class="variable">_command_index</span> <span class="keyword">in</span> <span class="number">0</span>..num_commands &#123;</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">command</span> = <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">pop_front</span>().<span class="title function_ invoke__">unwrap</span>();</span><br><span class="line">            <span class="keyword">let</span> <span class="variable">result</span> = <span class="keyword">match</span> command &#123;</span><br><span class="line">                Command::<span class="title function_ invoke__">Led</span>(id, _) =&gt; <span class="keyword">self</span></span><br><span class="line">                    .leds</span><br><span class="line">                    .<span class="title function_ invoke__">poll</span>(&amp;id)</span><br><span class="line">                    .<span class="title function_ invoke__">map_err</span>(|err| CommandError::<span class="title function_ invoke__">LedError</span>(err)),</span><br><span class="line">            &#125;;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">match</span> result &#123;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(())) =&gt; &#123;&#125;</span><br><span class="line">                Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err)) =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">return</span> Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Err</span>(err));</span><br><span class="line">                &#125;</span><br><span class="line">                Poll::Pending =&gt; &#123;</span><br><span class="line">                    <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">push_back</span>(command);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">self</span>.active_commands.<span class="title function_ invoke__">len</span>() == <span class="number">0</span> &#123;</span><br><span class="line">            Poll::<span class="title function_ invoke__">Ready</span>(<span class="title function_ invoke__">Ok</span>(()))</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            Poll::Pending</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now we only need to pre-define the <em>types</em> of actuators (e.g. LED, axis, spindle, relay, etc), not the names of the actuators. The user will give us something that defines the names. This is good!</p><p>If you’re still wondering why we still need all this boilerplate, it’s because we’re still avoiding heap allocations (<code>alloc</code>) and dynamic objects. Our code still only uses static objects where Rust knows the size of the objects at compile time, everything can be included on the stack, and everything is very efficient.</p><p>Depending on your situation, you could stop here. We solved the problems of generic hell and pre-defined shapes. We did all this while being maximally efficient for embedded devices.</p><p>But for my next trick, I’ll break these guarantees for the sake of a more ergonomic developer experience.</p><h2 id="TODO-💜">TODO 💜<a class="header-anchor" href="#TODO-💜">§</a></h2>]]></content>
    
    
    <summary type="html">As part of my work on Grid Bot “Tahi”, I finally figured out how to make the code for my robot re-usable as a library. Since to do this I needed to go on a deep journey into understanding Rust generic types, I thought I might share my learnings.</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>Polyledra v2: LED tensegrity</title>
    <link href="http://blog.mikey.nz/polyledra-v2-led-tensegrity/"/>
    <id>http://blog.mikey.nz/polyledra-v2-led-tensegrity/</id>
    <published>2021-01-30T11:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>(For v1, see <a href="/polyledra-v1-led-tetrahedron/">Polyledra v1: LED tetrahedron</a>)</p><h2 id="Inspiration">Inspiration<a class="header-anchor" href="#Inspiration">§</a></h2><blockquote><p>A tensegrity is a structural principle based on a system of isolated components under <em>compression</em> inside a network of continous <em>tension</em>, and arranged in such a way that the compressed members (struts) do not touch each other while the prestressed tensioned members (tendons) delineate the system spatially.</p><br /><p>The term was coined by <a href="https://en.wikipedia.org/wiki/Buckminster_Fuller">Buckminster Fuller</a> in the 1960s as a portmanteau of “tensional integrity”</p><ul><li><a href="https://en.wikipedia.org/wiki/Tensegrity">Tensegrity @ Wikipedia</a></li></ul></blockquote><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_tensegrity010.jpg 480w, /polyledra-v2-led-tensegrity/md_tensegrity010.jpg 768w, /polyledra-v2-led-tensegrity/lg_tensegrity010.jpg 992w, /polyledra-v2-led-tensegrity/xl_tensegrity010.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_tensegrity010.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_tensegrity010.jpg"  src="/polyledra-v2-led-tensegrity/tensegrity010.jpg" alt="" loading="lazy" width="360" height="426" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_tensegrity001.jpg 480w, /polyledra-v2-led-tensegrity/md_tensegrity001.jpg 768w, /polyledra-v2-led-tensegrity/lg_tensegrity001.jpg 992w, /polyledra-v2-led-tensegrity/xl_tensegrity001.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_tensegrity001.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_tensegrity001.jpg"  src="/polyledra-v2-led-tensegrity/tensegrity001.jpg" alt="" loading="lazy" width="360" height="320" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_tensegrity016.jpg 480w, /polyledra-v2-led-tensegrity/md_tensegrity016.jpg 768w, /polyledra-v2-led-tensegrity/lg_tensegrity016.jpg 992w, /polyledra-v2-led-tensegrity/xl_tensegrity016.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_tensegrity016.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_tensegrity016.jpg"  src="/polyledra-v2-led-tensegrity/tensegrity016.jpg" alt="" loading="lazy" width="360" height="480" /></div></p><h2 id="Tensegrity-prototype">Tensegrity prototype<a class="header-anchor" href="#Tensegrity-prototype">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_tensegrity-prototype-1.jpg 480w, /polyledra-v2-led-tensegrity/md_tensegrity-prototype-1.jpg 768w, /polyledra-v2-led-tensegrity/lg_tensegrity-prototype-1.jpg 992w, /polyledra-v2-led-tensegrity/xl_tensegrity-prototype-1.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_tensegrity-prototype-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_tensegrity-prototype-1.jpg"  src="/polyledra-v2-led-tensegrity/tensegrity-prototype-1.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_tensegrity-prototype-2.jpg 480w, /polyledra-v2-led-tensegrity/md_tensegrity-prototype-2.jpg 768w, /polyledra-v2-led-tensegrity/lg_tensegrity-prototype-2.jpg 992w, /polyledra-v2-led-tensegrity/xl_tensegrity-prototype-2.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_tensegrity-prototype-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_tensegrity-prototype-2.jpg"  src="/polyledra-v2-led-tensegrity/tensegrity-prototype-2.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544440?h=dc7b29a099&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-10-04) Polyledra v2: Tensegrity prototype"></div><h2 id="Controller">Controller<a class="header-anchor" href="#Controller">§</a></h2><p>This time I wanted to use more “off-the-shelf” LED software and hardware.</p><ul><li>Software: <a href="https://kno.wled.ge/">WLED</a></li><li>Hardware: <a href="https://quinled.info/pre-assembled-quinled-dig-uno/">QuinLED-Dig-Uno</a></li></ul><h2 id="Strut-design">Strut design<a class="header-anchor" href="#Strut-design">§</a></h2><p>Bill of materials:</p><ul><li>aluminium extruded round tube (12.7mm outer diameter, 0.9mm thickness)</li><li>acrylic round tube (40mm outer diameter)</li><li>4x 3d printed spacers</li><li>2x 3d printed end links</li><li>2x 3d printed end caps</li><li>XT60 connectors (power)</li><li>2 pin electrical cable (power)</li><li>WS2811 LED strips (White PCB, 5m 60 LEDs IP65)</li><li>3 pin 0.5mm^2 20AWG waterproof electrical cable (LED)</li><li>3 pin “small size” waterproof IP65 LED connector (LED)</li></ul><p>Benefits of aluminium tube inner:</p><ul><li>Acts as heatsink for LEDs</li><li>Can feed power cable through tube to other side</li></ul><p>Since I couldn’t find acrylic tubes long enough, I “welded” multiple tubes together with solvent cement.</p><h2 id="Strut-assembly">Strut assembly<a class="header-anchor" href="#Strut-assembly">§</a></h2><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796544501?h=f2a3973a72&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-13) Polyledra v2: 3D printing spacers"></div><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_printing-spacers.jpg 480w, /polyledra-v2-led-tensegrity/md_printing-spacers.jpg 768w, /polyledra-v2-led-tensegrity/lg_printing-spacers.jpg 992w, /polyledra-v2-led-tensegrity/xl_printing-spacers.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_printing-spacers.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_printing-spacers.jpg"  src="/polyledra-v2-led-tensegrity/printing-spacers.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796544455?h=675491f3ca&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-11) Polyledra v2: LED prototype"></div><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544480?h=2fc9288ae1&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-11) Polyledra v2: LED prototype"></div><p>The acrylic tubes are sanded to achieve a diffuser effect.</p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_sanding-acrylic-tubes.jpg 480w, /polyledra-v2-led-tensegrity/md_sanding-acrylic-tubes.jpg 768w, /polyledra-v2-led-tensegrity/lg_sanding-acrylic-tubes.jpg 992w, /polyledra-v2-led-tensegrity/xl_sanding-acrylic-tubes.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_sanding-acrylic-tubes.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_sanding-acrylic-tubes.jpg"  src="/polyledra-v2-led-tensegrity/sanding-acrylic-tubes.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_diffuser-test.jpg 480w, /polyledra-v2-led-tensegrity/md_diffuser-test.jpg 768w, /polyledra-v2-led-tensegrity/lg_diffuser-test.jpg 992w, /polyledra-v2-led-tensegrity/xl_diffuser-test.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_diffuser-test.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_diffuser-test.jpg"  src="/polyledra-v2-led-tensegrity/diffuser-test.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544524?h=d556bd9d33&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-17) Polyledra v2: Diffuser test"></div><h2 id="Double-doof-sticks">Double doof sticks<a class="header-anchor" href="#Double-doof-sticks">§</a></h2><p>I also made two portable “doof sticks” based on the same strut designs, with further 3d printed sections for: batteries, bi-directional power converter, power switch, power plug, WLED controller, and button interface. All solvent welded together.</p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_doof-stick-exploded.jpg 480w, /polyledra-v2-led-tensegrity/md_doof-stick-exploded.jpg 768w, /polyledra-v2-led-tensegrity/lg_doof-stick-exploded.jpg 992w, /polyledra-v2-led-tensegrity/xl_doof-stick-exploded.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_doof-stick-exploded.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_doof-stick-exploded.jpg"  src="/polyledra-v2-led-tensegrity/doof-stick-exploded.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796588810?h=371703bf57&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-27) Doof stick exploded"></div><p>Look amazing, but fragile.</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796584955?h=9398e57fc7&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2020-12-28) Double Doof Sticks"></div><h2 id="Tensegrity-design">Tensegrity design<a class="header-anchor" href="#Tensegrity-design">§</a></h2><p>Bill of materials:</p><ul><li>6x LED struts</li><li>3x 6x Tendons<ul><li>Shock cord</li><li>Shock cord end hooks</li></ul></li></ul><p>Each strut connects with:</p><ul><li>The neighbor struts at the top</li><li>The neighbor struts at the bottom</li><li>The neighbor struts from top to bottom</li></ul><p><div class="image-wrapper"><img src="/polyledra-v2-led-tensegrity/tensegrity.gif" alt="" loading="lazy" /></div></p><h2 id="Tensegrity-assembly">Tensegrity assembly<a class="header-anchor" href="#Tensegrity-assembly">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_soldered-edge.jpg 480w, /polyledra-v2-led-tensegrity/md_soldered-edge.jpg 768w, /polyledra-v2-led-tensegrity/lg_soldered-edge.jpg 992w, /polyledra-v2-led-tensegrity/xl_soldered-edge.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_soldered-edge.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_soldered-edge.jpg"  src="/polyledra-v2-led-tensegrity/soldered-edge.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_assembly-test-1.jpg 480w, /polyledra-v2-led-tensegrity/md_assembly-test-1.jpg 768w, /polyledra-v2-led-tensegrity/lg_assembly-test-1.jpg 992w, /polyledra-v2-led-tensegrity/xl_assembly-test-1.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_assembly-test-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_assembly-test-1.jpg"  src="/polyledra-v2-led-tensegrity/assembly-test-1.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_assembly-test-2.jpg 480w, /polyledra-v2-led-tensegrity/md_assembly-test-2.jpg 768w, /polyledra-v2-led-tensegrity/lg_assembly-test-2.jpg 992w, /polyledra-v2-led-tensegrity/xl_assembly-test-2.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_assembly-test-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_assembly-test-2.jpg"  src="/polyledra-v2-led-tensegrity/assembly-test-2.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_assembly-test-3.jpg 480w, /polyledra-v2-led-tensegrity/md_assembly-test-3.jpg 768w, /polyledra-v2-led-tensegrity/lg_assembly-test-3.jpg 992w, /polyledra-v2-led-tensegrity/xl_assembly-test-3.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_assembly-test-3.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_assembly-test-3.jpg"  src="/polyledra-v2-led-tensegrity/assembly-test-3.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544568?h=5507016050&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2021-01-12) Polyledra v2: Tensegrity test"></div><h2 id="Ignition">Ignition<a class="header-anchor" href="#Ignition">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_ignition.jpg 480w, /polyledra-v2-led-tensegrity/md_ignition.jpg 768w, /polyledra-v2-led-tensegrity/lg_ignition.jpg 992w, /polyledra-v2-led-tensegrity/xl_ignition.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_ignition.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_ignition.jpg"  src="/polyledra-v2-led-tensegrity/ignition.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><h2 id="Kiwiburn">Kiwiburn<a class="header-anchor" href="#Kiwiburn">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_kiwiburn-1.jpg 480w, /polyledra-v2-led-tensegrity/md_kiwiburn-1.jpg 768w, /polyledra-v2-led-tensegrity/lg_kiwiburn-1.jpg 992w, /polyledra-v2-led-tensegrity/xl_kiwiburn-1.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_kiwiburn-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_kiwiburn-1.jpg"  src="/polyledra-v2-led-tensegrity/kiwiburn-1.jpg" alt="" loading="lazy" width="1152" height="1536" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v2-led-tensegrity/sm_kiwiburn-2.jpg 480w, /polyledra-v2-led-tensegrity/md_kiwiburn-2.jpg 768w, /polyledra-v2-led-tensegrity/lg_kiwiburn-2.jpg 992w, /polyledra-v2-led-tensegrity/xl_kiwiburn-2.jpg 1280w, /polyledra-v2-led-tensegrity/2xl_kiwiburn-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v2-led-tensegrity/lg_kiwiburn-2.jpg"  src="/polyledra-v2-led-tensegrity/kiwiburn-2.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544606?h=e36ece3c4e&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2021-01-29) Polyledra v2: Kiwiburn"></div><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544637?h=03a12ba226&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2021-01-29) Polyledra v2: Kiwiburn"></div><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796544673?h=de776782f6&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2021-01-31) Polyledra v2: Kiwiburn"></div>]]></content>
    
    
    <summary type="html">An LED installation made from magic physics</summary>
    
    
    <content src="http://blog.mikey.nz/polyledra-v2-led-tensegrity/kiwiburn-1.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="led" scheme="http://blog.mikey.nz/tags/led/"/>
    
  </entry>
  
  <entry>
    <title>Portable solar v2</title>
    <link href="http://blog.mikey.nz/portable-solar-v2/"/>
    <id>http://blog.mikey.nz/portable-solar-v2/</id>
    <published>2021-01-23T11:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<p>I made a new portable solar setup, with help from <a href="https://www.mobile-solarpower.com/the-book.html">“Mobile Solar Power - Made Easy” by William Errol Prowse IV</a>.</p><p>(An evolution from <a href="http://localhost:4000/portable-solar-v1/">Portable Solar v1</a> and other learnings.)</p><p>The goal is to do a solar electric setup the “right way”:</p><ul><li>with a better solar charge controller</li><li>with an inverter</li><li>with best practice schematic for how to wire everything together</li><li>with appropriate circuit breakers and fuses</li><li>with appropriate thick wires</li><li>and so on…</li></ul><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796935636?h=167b282882" data-title="(2021-01-12) Portable Solar v2 : Build"></div><p><div class="image-wrapper"><img srcset="/portable-solar-v2/sm_mid-assembly.jpg 480w, /portable-solar-v2/md_mid-assembly.jpg 768w, /portable-solar-v2/lg_mid-assembly.jpg 992w, /portable-solar-v2/xl_mid-assembly.jpg 1280w, /portable-solar-v2/2xl_mid-assembly.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v2/lg_mid-assembly.jpg"  src="/portable-solar-v2/mid-assembly.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/portable-solar-v2/sm_assembled-inner.jpg 480w, /portable-solar-v2/md_assembled-inner.jpg 768w, /portable-solar-v2/lg_assembled-inner.jpg 992w, /portable-solar-v2/xl_assembled-inner.jpg 1280w, /portable-solar-v2/2xl_assembled-inner.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v2/lg_assembled-inner.jpg"  src="/portable-solar-v2/assembled-inner.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/portable-solar-v2/sm_solar-panels.jpg 480w, /portable-solar-v2/md_solar-panels.jpg 768w, /portable-solar-v2/lg_solar-panels.jpg 992w, /portable-solar-v2/xl_solar-panels.jpg 1280w, /portable-solar-v2/2xl_solar-panels.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v2/lg_solar-panels.jpg"  src="/portable-solar-v2/solar-panels.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p>TODO more detail</p>]]></content>
    
    
    <summary type="html">A new portable solar setup, the &quot;right way&quot;</summary>
    
    
    <content src="http://blog.mikey.nz/portable-solar-v2/solar-panels.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="solar" scheme="http://blog.mikey.nz/tags/solar/"/>
    
  </entry>
  
  <entry>
    <title>Polyledra v1: LED tetrahedron</title>
    <link href="http://blog.mikey.nz/polyledra-v1-led-tetrahedron/"/>
    <id>http://blog.mikey.nz/polyledra-v1-led-tetrahedron/</id>
    <published>2019-02-26T11:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>A <a href="https://en.wikipedia.org/wiki/Light-emitting_diode">light-emitting</a> <a href="https://en.wikipedia.org/wiki/Polyhedron">polyhedron</a> <a href="https://en.wikipedia.org/wiki/Chandelier">chandelier</a></p><span id="more"></span><p>Source: <a href="https://github.com/ahdinosaur/polyledra-v1"><code>ahdinosaur/polyledra-v1</code></a></p><h2 id="Background">Background<a class="header-anchor" href="#Background">§</a></h2><p>After playing with <a href="/pixels-for-the-pixel-god/">my portable rainbows</a>, I thought about my learning objectives for the next stage of portable rainbow exploration:</p><ul><li>I want to go <a href="https://github.com/ahdinosaur/pixelbeat/tree/bbb">back to the BeagleBone</a>, but this time using Rust instead of JavaScript</li><li>I want to get <a href="https://github.com/ahdinosaur/prusa-mendel">back into 3d printing</a> for enclosures and structures</li><li>I want to upgrade from breadboards to protoboards to custom pcb circuits, out-source soldering!</li><li>I want to play with graphics code</li></ul><p>I was sitting on a hill listening to music at a gathering in the forest last weekend, when I saw a 20-sided shape hanging over a stage, with fairy lights strung around the edges. ✨</p><p>I thought “what if I did the same with leds”? 🌈</p><p>I then continued to spend the rest of the festival obsessing about the shape, leds, and rust interfaces, which led to here. 🐱</p><h2 id="Shapes">Shapes<a class="header-anchor" href="#Shapes">§</a></h2><h3 id="Design-constraints">Design constraints<a class="header-anchor" href="#Design-constraints">§</a></h3><p>To simplify production, we want:</p><ul><li>MUST HAVE uniform length edges (easy for buying led strip channels)</li><li>COULD HAVE uniform angle patterns  (easier for making 3d printed joints)</li></ul><h3 id="Icosahedron"><a href="https://en.wikipedia.org/wiki/Regular_icosahedron">Icosahedron</a><a class="header-anchor" href="#Icosahedron">§</a></h3><p>The original gansta shape from the festival!</p><p>An icosahedron is a 20-sided shape which regular angle patterns and uniform length edges.</p><p>It’s also a gyroelgonated pentagonal dipryamid (my original understanding of the shape): on the top and bottom is a <a href="http://mathworld.wolfram.com/PentagonalPyramid.html">pentagonal pyramid</a>, in the middle is an <a href="https://en.wikipedia.org/wiki/Pentagonal_antiprism">pentagonal antiprism</a></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_shape-icosahedron.png 480w, /polyledra-v1-led-tetrahedron/md_shape-icosahedron.png 768w, /polyledra-v1-led-tetrahedron/lg_shape-icosahedron.png 992w, /polyledra-v1-led-tetrahedron/xl_shape-icosahedron.png 1280w, /polyledra-v1-led-tetrahedron/2xl_shape-icosahedron.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_shape-icosahedron.png"  src="/polyledra-v1-led-tetrahedron/shape-icosahedron.png" alt="" loading="lazy" width="714" height="731" /></div></p><h3 id="Octahedron"><a href="https://en.wikipedia.org/wiki/Octahedron">Octahedron</a><a class="header-anchor" href="#Octahedron">§</a></h3><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_shape-octahedron.png 480w, /polyledra-v1-led-tetrahedron/md_shape-octahedron.png 768w, /polyledra-v1-led-tetrahedron/lg_shape-octahedron.png 992w, /polyledra-v1-led-tetrahedron/xl_shape-octahedron.png 1280w, /polyledra-v1-led-tetrahedron/2xl_shape-octahedron.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_shape-octahedron.png"  src="/polyledra-v1-led-tetrahedron/shape-octahedron.png" alt="" loading="lazy" width="821" height="833" /></div></p><h3 id="Tetrahedron"><a href="https://en.wikipedia.org/wiki/Tetrahedron">Tetrahedron</a><a class="header-anchor" href="#Tetrahedron">§</a></h3><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_shape-tetrahedron.png 480w, /polyledra-v1-led-tetrahedron/md_shape-tetrahedron.png 768w, /polyledra-v1-led-tetrahedron/lg_shape-tetrahedron.png 992w, /polyledra-v1-led-tetrahedron/xl_shape-tetrahedron.png 1280w, /polyledra-v1-led-tetrahedron/2xl_shape-tetrahedron.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_shape-tetrahedron.png"  src="/polyledra-v1-led-tetrahedron/shape-tetrahedron.png" alt="" loading="lazy" width="753" height="690" /></div></p><h2 id="Initial-controller-dive">Initial controller dive<a class="header-anchor" href="#Initial-controller-dive">§</a></h2><h3 id="A-Rust-y-adventure">A Rust-y adventure<a class="header-anchor" href="#A-Rust-y-adventure">§</a></h3><p>Wow, Rust is legit!</p><p>Really enjoying how the compiler is so helpful.</p><p>Had my first fight with the borrow checker, still am on easy mode though. Had my first “spend hours writing a heap of rust code, finally compiles, and omg what it works!?”</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_rusty-tetrahedron.png 480w, /polyledra-v1-led-tetrahedron/md_rusty-tetrahedron.png 768w, /polyledra-v1-led-tetrahedron/lg_rusty-tetrahedron.png 992w, /polyledra-v1-led-tetrahedron/xl_rusty-tetrahedron.png 1280w, /polyledra-v1-led-tetrahedron/2xl_rusty-tetrahedron.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_rusty-tetrahedron.png"  src="/polyledra-v1-led-tetrahedron/rusty-tetrahedron.png" alt="" loading="lazy" width="565" height="638" /></div></p><p>I setup a basic multi-threaded message-passing architecture based on a conversation with Matt, thanks!</p><p>I wrote some code to derive pixel positions from an abstract shape, then on each clock tick it runs a function for each pixel that receives the position and returns the color, which are then all displayed in a 3d renderer.</p><p>I think the hard part of this project for me will be the graphics part, I find graphics code sooo confusing! At the moment was able to find a library that let me create shapes in 3d space without having to dive too deep. But I’m really keen to learn how to do proper graphics code, I forgot to mention that in my learning objectives above.</p><h3 id="More-Rust-y-learnings">More Rust-y learnings<a class="header-anchor" href="#More-Rust-y-learnings">§</a></h3><p>Been making heaps of progress, yay learning Rust!</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-02-19-18.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-02-19-18.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-02-19-18.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-02-19-18.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-02-19-18.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-02-19-18.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-02-19-18.png" alt="" loading="lazy" width="584" height="567" /></div></p><ul><li>in the simulator, render points instead of cubes for performance (until <a href="https://github.com/ahdinosaur/chandeledra/issues/1">I do my own gl</a>)</li><li>play with simple animated rgb scene</li><li>learn how non-blocking doesn’t make sense without an event loop</li><li>create a scene manager to handle switching between many scenes</li><li>hook up simulator window events so ‘left’ and ‘right’ switch previous and next scenes, respectively</li><li>support hsl colors</li><li>add simple rainbow scene</li><li>support scenes to return generic iterators, to auto-convert colors without intermediate vecs</li></ul><h3 id="Shape-walker">Shape walker<a class="header-anchor" href="#Shape-walker">§</a></h3><p>Had a long battle with the Rust borrow checker, ended up on top! 😅</p><p>Then moved on to the puzzle of how to implement a shape walker. 🌈</p><p><div class="image-wrapper"><img src="/polyledra-v1-led-tetrahedron/rainbow-tetrahedron.gif" alt="" width="489" height="475" loading="lazy" /></div></p><h3 id="From-software-to-3D-modeling">From software to 3D modeling<a class="header-anchor" href="#From-software-to-3D-modeling">§</a></h3><p>Moving from software to 3D modeling! 📐 🚥 🔆 🌈</p><p>First going to build a <a href="https://en.wikipedia.org/wiki/Tetrahedron">tetrahedron</a>, so I bought 20 aluminum led strip channels, each 0.5m long.</p><p>Here are the dimensions of each aluminum channel (except 500mm long):</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_aluminium-channel.jpg 480w, /polyledra-v1-led-tetrahedron/md_aluminium-channel.jpg 768w, /polyledra-v1-led-tetrahedron/lg_aluminium-channel.jpg 992w, /polyledra-v1-led-tetrahedron/xl_aluminium-channel.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_aluminium-channel.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_aluminium-channel.jpg"  src="/polyledra-v1-led-tetrahedron/aluminium-channel.jpg" alt="" loading="lazy" width="459" height="459" /></div></p><p>The idea is to have 3 led strip channels per edge of the tetrahedron so the edges will be lit from all angles. I had this idea before but was going to start with a single channel per edge, until I talked to my friend: she noticed that since the shapes will be regular, the best effects will come from seeing the other side of the shape <em>through</em> the shape!</p><p>With help from Jack, I made a 3d model of the tetrahedron connectors!</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_Screenshot_20180303_154737.png 480w, /polyledra-v1-led-tetrahedron/md_Screenshot_20180303_154737.png 768w, /polyledra-v1-led-tetrahedron/lg_Screenshot_20180303_154737.png 992w, /polyledra-v1-led-tetrahedron/xl_Screenshot_20180303_154737.png 1280w, /polyledra-v1-led-tetrahedron/2xl_Screenshot_20180303_154737.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_Screenshot_20180303_154737.png"  src="/polyledra-v1-led-tetrahedron/Screenshot_20180303_154737.png" alt="" loading="lazy" width="1536" height="809" /></div></p><h2 id="Getting-ready-for-Winter-Expo">Getting ready for Winter Expo<a class="header-anchor" href="#Getting-ready-for-Winter-Expo">§</a></h2><p>Mix helped me with the tetrahedron angles math, my last edge connector was so wrong 📐</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-vertex-structure-math.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-vertex-structure-math.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-math.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-vertex-structure-math.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-vertex-structure-math.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-math.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-vertex-structure-math.jpg" alt="" loading="lazy" width="1500" height="783" /></div></p><p>Then I completely re-structured the vertex structure, so can fit wires inside and round the back</p><p>(<a href="https://github.com/ahdinosaur/polyledra-v1/blob/6d7f562fed9a5393606230402887901394d0b97c/vertex-structure/cad/tetrahedron.scad">scad</a>, <a href="https://github.com/ahdinosaur/polyledra-v1/blob/6d7f562fed9a5393606230402887901394d0b97c/vertex-structure/stl/tetrahedron.stl">viewable stl</a>)</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-vertex-structure-scad.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-vertex-structure-scad.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-scad.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-vertex-structure-scad.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-vertex-structure-scad.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-scad.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-vertex-structure-scad.png" alt="" loading="lazy" width="934" height="761" /></div></p><p>Then I made a new controller scene 🎇</p><p>(<a href="https://github.com/ahdinosaur/chandeledra/blob/6d7f562fed9a5393606230402887901394d0b97c/controller-app/src/scene/spark.rs">rs</a>)</p><p><div class="image-wrapper"><img src="/polyledra-v1-led-tetrahedron/chandeledra-spark-loop.gif" alt="" width="450" height="516" loading="lazy" /></div></p><p>Then I got my code running on the <a href="https://beagleboard.org/pocket">Pocket Beagle</a>. I love Rust where I can write code on my laptop (which doesn’t have access to an spi interface necessary to control the leds), then once I had it compile on my laptop (without ever running the code) there was only a small configuration change to make it actually work on the Pocket, yay compile-time type and borrow checking!</p><p>So this weekend I got the controller rust code running on the Pocket Beagle displaying on some real led hardware, with help from Piet who introduced me to <a href="https://github.com/japaric/cross">japaric/cross</a>, OMG so great.</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/795754043?h=d35915c222&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-06-02) Polyledra v1: Work In Progress"></div><p>Now I’ve prepared 15/18 led strips in aluminium channels, made an easy deploy script from my computer to the Pocket, setup the controller binary to run automatically when the Pocket starts, and fixed the code so it outputs pixel data for 3 “arms” per tetrahedron edge (3 arms per edge * 6 edges = 18 total arms).</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-arms.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-arms.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-arms.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-arms.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-arms.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-arms.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-arms.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p>Next up:</p><ul><li>setup power injections at regular intervals across the shape (at least 3, for 180 leds per injection)</li><li>test the current vertex structure print actually works, tune until good</li></ul><p>Fun learning: using rust’s <code>--release</code> flag led to a 10x performance increase, wow! 🌟</p><p>Keen to get this ready for the <a href="https://tube.arthack.nz/videos/watch/752e1176-8854-47b5-8055-ca861b1b6fe0">Art~Hack Wellington Winter Expo</a>. 😅</p><h2 id="Continued">Continued<a class="header-anchor" href="#Continued">§</a></h2><p>Yesterday fixed up the edge connector model based on feedback from my friend Jack who generously did a print some time ago. then played around with a new scene using noise functions, doesn’t look good yet but is pretty fun to play with.  🌊</p><p>Today soldered up the <a href="https://www.seeedstudio.com/AllPixel-Power-Tap-Kit-p-2380.html">power injectors</a> (had these leftover from a previous project, they connect perfectly here!) and powered up all the leds, but turns out I had an off-by-one error! Notice the center point is no longer in the center. The reason was float math, <code>0.999996</code> when expecting <code>1</code>, solution was to round by a decimal place.</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-off-by-one.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-off-by-one.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-off-by-one.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-off-by-one.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-off-by-one.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-off-by-one.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-off-by-one.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Then based on a tip from Piet, I sprayed the strips down with <a href="https://www.jaycar.co.nz/circuit-board-lacquer-spray-can/p/NA1002">circuit board lacquer</a> so they will be less likely to short (the aluminum is anodized, but scratch under the surface and you have a conductive metal touching the copper on the strips).</p><p>Then put on the diffusers and connected everything again, organized the “arms” by edge, even though it’s not yet in the shape of a tetrahedron. Goodness, I’ve never had a project be this clean and maybe even <em>legit</em>!</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/795754074?h=b4f0cc9d77&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-06-04) Polyledra v1: Work In Progress"></div><p>And all powered by this cute linux computer! 🐶</p><p>(And some other things, see <a href="https://github.com/ahdinosaur/chandeledra/blob/de8ad2b9137e729acb819ddc46ce12246a832355/BOM.md">complete bill of materials</a>)</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-cute-pocket-beagle.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-cute-pocket-beagle.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-cute-pocket-beagle.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-cute-pocket-beagle.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-cute-pocket-beagle.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-cute-pocket-beagle.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-cute-pocket-beagle.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><h2 id="More-updates">More updates!<a class="header-anchor" href="#More-updates">§</a></h2><p>Here’s my new scene using 4-dimensional noise to determine colors! (<code>[x, y, z, time]</code> where time oscillates back and forth on each “beat” (TODO), slowly steps forward), got some help from Jack at <a href="https://arthack.nz">Art~Hack</a>.</p><p><div class="image-wrapper"><img src="/polyledra-v1-led-tetrahedron/chandeledra-glow.gif" alt="" width="376" height="424" loading="lazy" /></div></p><p>Then added a button to change modes, except since I didn’t have an actual button I just tap the wires together. 😸</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/795754109?h=c766eaca31&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-06-07) Polyledra v1: Work In Progress"></div><p>Yesterday, thanks to the other Jack, got the third print of the vertex structure, third time’s a charm! (actually this design needed changes, the 4th print looks good so far.)</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-vertex-structure-print-3.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-vertex-structure-print-3.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-print-3.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-vertex-structure-print-3.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-vertex-structure-print-3.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-structure-print-3.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-vertex-structure-print-3.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Mix helped me shape out the tetrahedron:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-mix-structure.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-mix-structure.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-mix-structure.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-mix-structure.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-mix-structure.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-mix-structure.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-mix-structure.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Then soldered some wires and used the 4th print to assemble a partial tetrahedron, it’s almost a thing!</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-bright-in-progress.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-bright-in-progress.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-bright-in-progress.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-bright-in-progress.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-bright-in-progress.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-bright-in-progress.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-bright-in-progress.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>In motion!</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/795754216?h=0c7296dc2b&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-06-09) Polyledra v1: Work In Progress"></div><p>I’m still in awe that any of this is working, it’s more beautiful than I deserve. 💖</p><h2 id="Demo">Demo<a class="header-anchor" href="#Demo">§</a></h2><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/795507373?h=f6ab7523c6&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-06-29) Polyledra v1: Demo"></div><h2 id="Another-splash-of-updates">Another splash of updates<a class="header-anchor" href="#Another-splash-of-updates">§</a></h2><p>Another splash of updates! 🐋</p><p>I disassembled the tetrahedron and brought it with me to California. ☀ a bunch of wires soldered to the led strips broke when “unplugging” the led channels from the connector. So to make future dis-assembly and re-assembly less painful, I tried a new idea: what if the 3 led strips for a given edge connected to a edge part, and then 3 edge parts connected to a vertex part.</p><p>Here’s my first plug and socket design:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-edge-plug-v1.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-edge-plug-v1.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-edge-plug-v1.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-edge-plug-v1.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-edge-plug-v1.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-edge-plug-v1.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-edge-plug-v1.png" alt="" loading="lazy" width="303" height="380" /></div><br><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-vertex-socket-v1.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-vertex-socket-v1.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-socket-v1.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-vertex-socket-v1.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-vertex-socket-v1.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-socket-v1.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-vertex-socket-v1.png" alt="" loading="lazy" width="427" height="326" /></div></p><p>The “plug” design here was especially bad because a 3d printed part gets strength from horizontal, not vertical. So while I could connected the “plug” into the “socket”, given the lack of tolerance I pushed them tightly together and <em>snap</em> the “plug” broke off.</p><p>And then I realized, this is what threaded bolts 🔩 are for:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-edge-plug-v2.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-edge-plug-v2.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-edge-plug-v2.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-edge-plug-v2.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-edge-plug-v2.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-edge-plug-v2.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-edge-plug-v2.png" alt="" loading="lazy" width="396" height="363" /></div><br><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-vertex-socket-v2.png 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-vertex-socket-v2.png 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-socket-v2.png 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-vertex-socket-v2.png 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-vertex-socket-v2.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-vertex-socket-v2.png"  src="/polyledra-v1-led-tetrahedron/chandeledra-vertex-socket-v2.png" alt="" loading="lazy" width="550" height="335" /></div></p><p>With this design, the edges are meant to be portable as units, so I discovered hot glue on both sides to prevent the wire connection from breaking and to keep out dust. 🌈</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-reassembly.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-reassembly.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-reassembly.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-reassembly.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-reassembly.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-reassembly.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-reassembly.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Then to bring to Burning Man, I found a bag and added 2 portable usb power packs, 🔋</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-usb-battery-pack.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-usb-battery-pack.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-usb-battery-pack.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-usb-battery-pack.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-usb-battery-pack.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-usb-battery-pack.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-usb-battery-pack.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p>Added copious amounts of tape, to robustify the setup in danger of my lack of repair tooling,</p><p>and hung the polyledra as a chandelier from rope 🐍</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-hanging.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-hanging.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-hanging.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-hanging.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-hanging.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-hanging.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-hanging.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>and later at the burn, tied to my bike. 🚲</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_chandeledra-bike.jpg 480w, /polyledra-v1-led-tetrahedron/md_chandeledra-bike.jpg 768w, /polyledra-v1-led-tetrahedron/lg_chandeledra-bike.jpg 992w, /polyledra-v1-led-tetrahedron/xl_chandeledra-bike.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_chandeledra-bike.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_chandeledra-bike.jpg"  src="/polyledra-v1-led-tetrahedron/chandeledra-bike.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><div class="video-embed" data-ratio="9:16" data-type="vimeo" data-src="https://player.vimeo.com/video/796157114?h=123cc74709&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-09-02) Polyledra v1: Burning Man"></div><p>This all landed swimmingly,</p><ul><li>nothing went wrong at the burn</li><li>the batteries lasted all night, no problem</li><li>one vertex part broke, but the tape kept everything together, as if nothing happened</li><li>worked well<ul><li>hanging from rope at our home camp</li><li>dancing with me and flowing around</li><li>riding on a bike</li></ul></li></ul><p>Yay! 💃</p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796157718?h=008898648a&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2018-10-02) Polyledra v1: Demo"></div><p>Then dis-assembled to bring back to New Zealand (during which time another two vertex parts broke), got replacements vertex parts printed at <a href="https://natlib.govt.nz/visiting/wellington/3d-printing">National Library Wellington</a>, and re-assembled again for Art~Hack Spring Expo / Maker Faire Wellington.</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_arthack-spring-expo-maker-faire.jpg 480w, /polyledra-v1-led-tetrahedron/md_arthack-spring-expo-maker-faire.jpg 768w, /polyledra-v1-led-tetrahedron/lg_arthack-spring-expo-maker-faire.jpg 992w, /polyledra-v1-led-tetrahedron/xl_arthack-spring-expo-maker-faire.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_arthack-spring-expo-maker-faire.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_arthack-spring-expo-maker-faire.jpg"  src="/polyledra-v1-led-tetrahedron/arthack-spring-expo-maker-faire.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Now to get ready for “I-Can’t-Believe-It’s-Not-Kiwiburn”:</p><ul><li>replace the wires with custom pcbs<ul><li>1 per vertex</li><li>2 per edge (one on each side)</li></ul></li><li>fix the strength of the vertex part (by splitting the angle into a separate part which is printed sideways)</li><li>make it waterproof (ready for the New Zealand outdoors)</li><li>more frequent power injection (something goes wrong at higher power)</li></ul><h2 id="Rambles-to-share">Rambles to share<a class="header-anchor" href="#Rambles-to-share">§</a></h2><p>I figured that printing a custom PCB was highest priority, because the time between iterations could be up to a month, if I needed to have something printed overseas.</p><p>Okay, I played with KiCad before, I liked what it was doing but I didn’t like using the graphical interface, I wanted to think in terms of math (code). I was used to using OpenSCAD for my 3d modelling, I was used to writing code for physical objects, why not code for circuits?</p><p>I quickly realized KiCad used <a href="https://en.wikipedia.org/wiki/S-expression">S-expressions</a> to represent PCB components and boards. What if I wrote JavaScript which a read JavaScript config to generate a Kicad file?</p><p>So yeah: I made <a href="https://github.com/ahdinosaur/jseda"><code>jseda</code></a> &amp; <a href="https://github.com/ahdinosaur/sexprs"><code>sexprs</code></a> 😻</p><p>And made my first circuit with code:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_first-jseda-polyledra-edge-a.png 480w, /polyledra-v1-led-tetrahedron/md_first-jseda-polyledra-edge-a.png 768w, /polyledra-v1-led-tetrahedron/lg_first-jseda-polyledra-edge-a.png 992w, /polyledra-v1-led-tetrahedron/xl_first-jseda-polyledra-edge-a.png 1280w, /polyledra-v1-led-tetrahedron/2xl_first-jseda-polyledra-edge-a.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_first-jseda-polyledra-edge-a.png"  src="/polyledra-v1-led-tetrahedron/first-jseda-polyledra-edge-a.png" alt="" loading="lazy" width="945" height="814" /></div></p><p>Which later became refined to the 4 circuits I need:</p><p><em>Edge, side a</em>:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-jseda-circuit-edge-a.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-jseda-circuit-edge-a.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-edge-a.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-jseda-circuit-edge-a.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-jseda-circuit-edge-a.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-edge-a.png"  src="/polyledra-v1-led-tetrahedron/polyledra-jseda-circuit-edge-a.png" alt="" loading="lazy" width="882" height="762" /></div></p><p><em>Edge, side b</em>:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-jseda-circuit-edge-b.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-jseda-circuit-edge-b.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-edge-b.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-jseda-circuit-edge-b.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-jseda-circuit-edge-b.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-edge-b.png"  src="/polyledra-v1-led-tetrahedron/polyledra-jseda-circuit-edge-b.png" alt="" loading="lazy" width="887" height="764" /></div></p><p><em>Tetrahedron vertex</em>:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-jseda-circuit-tetrahedron-vertex.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-jseda-circuit-tetrahedron-vertex.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-tetrahedron-vertex.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-jseda-circuit-tetrahedron-vertex.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-jseda-circuit-tetrahedron-vertex.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-tetrahedron-vertex.png"  src="/polyledra-v1-led-tetrahedron/polyledra-jseda-circuit-tetrahedron-vertex.png" alt="" loading="lazy" width="892" height="769" /></div></p><p><em>Octahedron vertex</em>:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-jseda-circuit-octahedron-vertex.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-jseda-circuit-octahedron-vertex.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-octahedron-vertex.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-jseda-circuit-octahedron-vertex.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-jseda-circuit-octahedron-vertex.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-jseda-circuit-octahedron-vertex.png"  src="/polyledra-v1-led-tetrahedron/polyledra-jseda-circuit-octahedron-vertex.png" alt="" loading="lazy" width="826" height="816" /></div></p><p>In this time I discovered that my whole time in Wellington (~4 years), during which time I lamented the lack of a local hackerspace, there has been a publicly-available Fab Lab: <a href="https://fablabwgtn.co.nz/">Fab Lab Wgtn</a>, complete with 3D printers, laser cutters, multiple CNC machines, a PCB mill, and more. 😋</p><p>So I was hoping to prototype my circuits on the mill, even got help from Craig to use the mill. I ended up procrastinating on that front, so while the clock was ticking and the festivals approaching I decided to place my bets on my design and get my circuits made from <a href="https://seeedstudio.com/">Seeed Studios</a>.</p><p>Today the circuits arrived, along with 3D prints I outsourced to be printed with PETG (stronger and more weatherproof than PLA)!</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-circuits.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-circuits.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-circuits.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-circuits.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-circuits.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-circuits.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-circuits.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>So far everything looks sweet as, I’m very excited. 😊 if everything checks out, I’ll be able to make a few more tetrahedrons, maybe an octahedron, with far better structural and electrical reliability than my current prototype, yay! 💃</p><h2 id="3D-printers-are-happy">3D printers are happy<a class="header-anchor" href="#3D-printers-are-happy">§</a></h2><p>Before I learned about Fab Lab Wgtn, I bought a 3D printer, which took a few months to arrive but finally did, it’s amaze. Back in the day I built a <a href="https://reprap.org/wiki/Prusa_Mendel">Prusa Mendel</a> from a kit, which at the time I knew meant that my time and energy would be focused on the 3D printer itself. These days I wanted to focus on 3D printing, and Josef Prusa made a company selling their printers, so I bought a pre-assembled <a href="https://shop.prusa3d.com/en/17-3d-printers">Prusa i3 MK3</a>, I couldn’t be happier. 🔩</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_dinosaur-prusa-i3-mk3-3d-printer.jpg 480w, /polyledra-v1-led-tetrahedron/md_dinosaur-prusa-i3-mk3-3d-printer.jpg 768w, /polyledra-v1-led-tetrahedron/lg_dinosaur-prusa-i3-mk3-3d-printer.jpg 992w, /polyledra-v1-led-tetrahedron/xl_dinosaur-prusa-i3-mk3-3d-printer.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_dinosaur-prusa-i3-mk3-3d-printer.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_dinosaur-prusa-i3-mk3-3d-printer.jpg"  src="/polyledra-v1-led-tetrahedron/dinosaur-prusa-i3-mk3-3d-printer.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p>Meanwhile, I had a new idea to fix the vertex part, again keeping in the mind that the strength in a 3d printed part is printed layer by layer:</p><p><a href="https://youtu.be/SyXvFngkf1Q">https://youtu.be/SyXvFngkf1Q</a></p><p>Instead of printing the angles of the vertex part as one, I made a separate part with the angle, to be printed on the side, maximizing strength. 💪</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-structure-vertex-angle.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-structure-vertex-angle.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-structure-vertex-angle.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-structure-vertex-angle.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-structure-vertex-angle.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-structure-vertex-angle.png"  src="/polyledra-v1-led-tetrahedron/polyledra-structure-vertex-angle.png" alt="" loading="lazy" width="750" height="467" /></div></p><p>It worked, but meant I’d have another 3 sets of connectors to keep everything together. After some time exploring this new approach, I went back to the old approach and while not perfect I think it’s stronger than my last design and good enough for my purposes.</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-structure-vertex-angle-2.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-structure-vertex-angle-2.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-structure-vertex-angle-2.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-structure-vertex-angle-2.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-structure-vertex-angle-2.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-structure-vertex-angle-2.png"  src="/polyledra-v1-led-tetrahedron/polyledra-structure-vertex-angle-2.png" alt="" loading="lazy" width="957" height="857" /></div></p><p>While I’ve been printing away, I ended up with PLA prototypes, wondering what to do with them. PLA is technically compostable and recyclable, but in practice you need a industrial composter (which is not available here in Wellington) and most recycling systems don’t accept PLA. Despite this reality, PLA is commonly used by your eco-friendly cafes as “compostable” coffee lids. Anyways, I’ve started re-using my PLA prototypes to make party jewelry, which has been working really well because my 3D designs are very symmetric and have appealing features (like the edge connector can look like a few faces, depending on how you angle it).</p><p>During this time, I also discovered an <a href="https://github.com/benjamin-edward-morgan/openscad-polyhedra">OpenSCAD library for polyhedra</a>, which let me visualize the entire tetrahedron, with all the parts put together:</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-tetrahedron-2.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-tetrahedron-2.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-2.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-tetrahedron-2.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-tetrahedron-2.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-2.png"  src="/polyledra-v1-led-tetrahedron/polyledra-tetrahedron-2.png" alt="" loading="lazy" width="853" height="776" /></div></p><p>Oh, also I went down a side quest in the search for waterproofing, discovered the magic of o-rings and how they are used everywhere, I had no idea! I played around with making custom o-rings from making 3d models of molds to fill with rubber, Haven’t given this a hoon yet though.</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-o-gon.png 480w, /polyledra-v1-led-tetrahedron/md_polyledra-o-gon.png 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-o-gon.png 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-o-gon.png 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-o-gon.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-o-gon.png"  src="/polyledra-v1-led-tetrahedron/polyledra-o-gon.png" alt="" loading="lazy" width="988" height="706" /></div></p><h2 id="Next-iteration">Next iteration<a class="header-anchor" href="#Next-iteration">§</a></h2><p>So far the next iteration of the tetrahedron is going well! 🌈</p><p>(Not pictured: all the time spent making a mess in the empty office while assembling and listening to drum &amp; bass, or when I soldered things backwards and had to unsolder everything, or when twice I plugged the power plugs backwards (+5V into GND and vise versa) causing the wires to rapidly melt and burn))</p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-two-edges.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-two-edges.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-two-edges.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-two-edges.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-two-edges.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-two-edges.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-two-edges.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-tetrahedron-circuits.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-tetrahedron-circuits.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-circuits.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-tetrahedron-circuits.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-tetrahedron-circuits.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-circuits.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-tetrahedron-circuits.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-edges.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-edges.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-edges.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-edges.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-edges.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-edges.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-edges.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-partial-tetrahedron.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-partial-tetrahedron.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-partial-tetrahedron.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-partial-tetrahedron.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-partial-tetrahedron.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-partial-tetrahedron.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-partial-tetrahedron.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-tetrahedron-3.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-tetrahedron-3.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-3.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-tetrahedron-3.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-tetrahedron-3.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-3.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-tetrahedron-3.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-tetrahedron-4.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-tetrahedron-4.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-4.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-tetrahedron-4.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-tetrahedron-4.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-tetrahedron-4.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-tetrahedron-4.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><h2 id="Twisted">Twisted<a class="header-anchor" href="#Twisted">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-twisted.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-twisted.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-twisted.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-twisted.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-twisted.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-twisted.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-twisted.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796156160?h=ebc56605c9&&autoplay=1&loop=1&autopause=0&muted=1" data-title="(2019-01-01) Polyledra v1: Twisted"></div><h2 id="Nowhere">Nowhere<a class="header-anchor" href="#Nowhere">§</a></h2><div class="video-wrapper"><video autoplay loop playsinline muted width="640" height="480">  <source src="/polyledra-v1-led-tetrahedron/nowhere-renegade.mp4" /></video></div><h2 id="Double-trouble">Double trouble<a class="header-anchor" href="#Double-trouble">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-all-the-pcbs-1.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-all-the-pcbs-1.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-all-the-pcbs-1.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-all-the-pcbs-1.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-all-the-pcbs-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-all-the-pcbs-1.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-all-the-pcbs-1.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-all-the-pcbs-2.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-all-the-pcbs-2.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-all-the-pcbs-2.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-all-the-pcbs-2.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-all-the-pcbs-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-all-the-pcbs-2.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-all-the-pcbs-2.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_polyledra-double-trouble.jpg 480w, /polyledra-v1-led-tetrahedron/md_polyledra-double-trouble.jpg 768w, /polyledra-v1-led-tetrahedron/lg_polyledra-double-trouble.jpg 992w, /polyledra-v1-led-tetrahedron/xl_polyledra-double-trouble.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_polyledra-double-trouble.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_polyledra-double-trouble.jpg"  src="/polyledra-v1-led-tetrahedron/polyledra-double-trouble.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><h2 id="“I-Can’t-Believe-It’s-Not-Kiwiburn”">“I-Can’t-Believe-It’s-Not-Kiwiburn”<a class="header-anchor" href="#“I-Can’t-Believe-It’s-Not-Kiwiburn”">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_ignition-abundance.jpg 480w, /polyledra-v1-led-tetrahedron/md_ignition-abundance.jpg 768w, /polyledra-v1-led-tetrahedron/lg_ignition-abundance.jpg 992w, /polyledra-v1-led-tetrahedron/xl_ignition-abundance.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_ignition-abundance.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_ignition-abundance.jpg"  src="/polyledra-v1-led-tetrahedron/ignition-abundance.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_ignition-night.jpg 480w, /polyledra-v1-led-tetrahedron/md_ignition-night.jpg 768w, /polyledra-v1-led-tetrahedron/lg_ignition-night.jpg 992w, /polyledra-v1-led-tetrahedron/xl_ignition-night.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_ignition-night.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_ignition-night.jpg"  src="/polyledra-v1-led-tetrahedron/ignition-night.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><h2 id="Scuttle-camp">Scuttle camp<a class="header-anchor" href="#Scuttle-camp">§</a></h2><p><div class="image-wrapper"><img srcset="/polyledra-v1-led-tetrahedron/sm_scuttle-camp.jpg 480w, /polyledra-v1-led-tetrahedron/md_scuttle-camp.jpg 768w, /polyledra-v1-led-tetrahedron/lg_scuttle-camp.jpg 992w, /polyledra-v1-led-tetrahedron/xl_scuttle-camp.jpg 1280w, /polyledra-v1-led-tetrahedron/2xl_scuttle-camp.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/polyledra-v1-led-tetrahedron/lg_scuttle-camp.jpg"  src="/polyledra-v1-led-tetrahedron/scuttle-camp.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p>]]></content>
    
    
    <summary type="html">A light-emitting polyhedron chandelier</summary>
    
    
    <content src="http://blog.mikey.nz/polyledra-v1-led-tetrahedron/polyledra-tetrahedron-3.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="led" scheme="http://blog.mikey.nz/tags/led/"/>
    
  </entry>
  
  <entry>
    <title>A burn dance</title>
    <link href="http://blog.mikey.nz/a-burn-dance/"/>
    <id>http://blog.mikey.nz/a-burn-dance/</id>
    <published>2018-08-17T12:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>Playing around with rainbows LEDs i can dance with!  🌈💃⚡️💖</p><p>In the classic shape of suspenders and a belt.</p><span id="more"></span><div class="video-embed" data-ratio="2:3" data-type="vimeo" data-src="https://player.vimeo.com/video/795074597?h=fa397d5ebc" data-title="(2018) A Burn Dance: Walkthrough"></div><p>Continuing from <a href="/pixels-for-the-pixel-god/">PIXELS FOR THE PIXEL GOD</a></p><p>Source: <a href="https://github.com/ahdinosaur/aburndance"><code>ahdinosaur/aburndance</code></a></p><p><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_202947.jpg 480w, /a-burn-dance/md_IMG_20180817_202947.jpg 768w, /a-burn-dance/lg_IMG_20180817_202947.jpg 992w, /a-burn-dance/xl_IMG_20180817_202947.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_202947.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_202947.jpg"  src="/a-burn-dance/IMG_20180817_202947.jpg" alt="" loading="lazy" width="1536" height="2048" /></div></p><h2 id="BOM">BOM<a class="header-anchor" href="#BOM">§</a></h2><p>Bill of materials:</p><ul><li>led strips: <a href="https://www.adafruit.com/product/2239?length=2">apa102</a><ul><li><a href="https://www.amazon.com/gp/product/B0777BQC1P/">connector</a></li></ul></li><li>controller: <a href="https://www.adafruit.com/product/3405">Adafruit HUZZAH32 – ESP32 Feather Board</a><ul><li><a href="https://www.adafruit.com/product/2884">Feather proto board</a></li><li><a href="https://www.adafruit.com/product/377">rotary encoder</a></li><li><a href="https://www.adafruit.com/product/367">tactile button</a></li></ul></li><li>usb power source: <a href="https://www.amazon.com/dp/B01JIWQPMW">Anker PowerCore 26800</a></li><li>shoulder straps (suspenders): <a href="https://www.etsy.com/nz/listing/456446760/handmade-usa-blackbrowntan-leather-clip">etsy</a></li><li>waist strap (belt): <a href="https://www.etsy.com/nz/listing/114576723/handmade-thick-leather-belt-mens-womens">etsy</a></li></ul><h2 id="User-interface-design">User interface design<a class="header-anchor" href="#User-interface-design">§</a></h2><p>Controller has a current mode and current param.</p><p>The current mode is being rendered with all the params for that mode.</p><p>Here are the available ways to interface with the controller:</p><ul><li>press 1st button for previous mode</li><li>press 2nd button for next mode</li><li>press 3rd button for previous param</li><li>press 4th button for next param</li><li>turn the rotary encoder to change the current param</li><li>hold 1st button until white then turn rotary encoder to change brightness</li><li>hold 2nd button until white then turn rotary encoder to change color temperature</li></ul><h2 id="Showcase">Showcase<a class="header-anchor" href="#Showcase">§</a></h2><p><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_141408.jpg 480w, /a-burn-dance/md_IMG_20180817_141408.jpg 768w, /a-burn-dance/lg_IMG_20180817_141408.jpg 992w, /a-burn-dance/xl_IMG_20180817_141408.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_141408.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_141408.jpg"  src="/./a-burn-dance/IMG_20180817_141408.jpg" alt="" loading="lazy" width="1536" height="1152" /></div><br><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_141417.jpg 480w, /a-burn-dance/md_IMG_20180817_141417.jpg 768w, /a-burn-dance/lg_IMG_20180817_141417.jpg 992w, /a-burn-dance/xl_IMG_20180817_141417.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_141417.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_141417.jpg"  src="/./a-burn-dance/IMG_20180817_141417.jpg" alt="" loading="lazy" width="1536" height="1152" /></div><br><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_144718.jpg 480w, /a-burn-dance/md_IMG_20180817_144718.jpg 768w, /a-burn-dance/lg_IMG_20180817_144718.jpg 992w, /a-burn-dance/xl_IMG_20180817_144718.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_144718.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_144718.jpg"  src="/./a-burn-dance/IMG_20180817_144718.jpg" alt="" loading="lazy" width="1536" height="1152" /></div><br><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_163534.jpg 480w, /a-burn-dance/md_IMG_20180817_163534.jpg 768w, /a-burn-dance/lg_IMG_20180817_163534.jpg 992w, /a-burn-dance/xl_IMG_20180817_163534.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_163534.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_163534.jpg"  src="/./a-burn-dance/IMG_20180817_163534.jpg" alt="" loading="lazy" width="1536" height="2048" /></div><br><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180817_195256.jpg 480w, /a-burn-dance/md_IMG_20180817_195256.jpg 768w, /a-burn-dance/lg_IMG_20180817_195256.jpg 992w, /a-burn-dance/xl_IMG_20180817_195256.jpg 1280w, /a-burn-dance/2xl_IMG_20180817_195256.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180817_195256.jpg"  src="/./a-burn-dance/IMG_20180817_195256.jpg" alt="" loading="lazy" width="1536" height="2048" /></div><br><div class="image-wrapper"><img srcset="/a-burn-dance/sm_IMG_20180819_152242.jpg 480w, /a-burn-dance/md_IMG_20180819_152242.jpg 768w, /a-burn-dance/lg_IMG_20180819_152242.jpg 992w, /a-burn-dance/xl_IMG_20180819_152242.jpg 1280w, /a-burn-dance/2xl_IMG_20180819_152242.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-burn-dance/lg_IMG_20180819_152242.jpg"  src="/./a-burn-dance/IMG_20180819_152242.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><div class="video-embed" data-ratio="2:3" data-type="vimeo" data-src="https://player.vimeo.com/video/795749915?h=768bef89b0" data-title="(2018) A Burn Dance: Demo"></div><h2 id="Resources">Resources<a class="header-anchor" href="#Resources">§</a></h2><ul><li><a href="https://learn.adafruit.com/adafruit-huzzah32-esp32-feather">Adafruit ESP32 learning resources</a><ul><li><a href="https://learn.adafruit.com/adafruit-huzzah32-esp32-feather/pinouts">ESP32 pinouts</a></li><li><a href="https://learn.adafruit.com/adafruit-huzzah32-esp32-feather/using-with-arduino-ide">ESP32 guide to installing the Arduino IDE</a>.</li></ul></li><li><a href="https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino">arduino-esp32/…/examples/…/RepeatTimer.ino</a></li></ul>]]></content>
    
    
    <summary type="html">Playing around with rainbows LEDs i can dance with! 🌈💃⚡️💖

In the classic shape of suspenders and a belt.</summary>
    
    
    <content src="http://blog.mikey.nz/a-burn-dance/IMG_20180817_202947.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Empathetic vs Skeptical Moderates</title>
    <link href="http://blog.mikey.nz/empathetic-vs-skeptical-moderates/"/>
    <id>http://blog.mikey.nz/empathetic-vs-skeptical-moderates/</id>
    <published>2018-04-23T10:07:17.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>In the last year, I’ve noticed two seas of (white) moderates: one as a valuable bridge, another as an infection of deception.</p><span id="more"></span><p>For context, I come from the United States of America, where many of my extended family and friends may support Donald Trump and may be on the spectrum of the Alt-Right, it’s not possible for me to ignore the new waves of moderate politics.</p><p>At some point a distinction came to light, so I’d like to share what I’ve learned about moderates, good and bad.</p><p><a href="https://upload.wikimedia.org/wikipedia/commons/c/c9/Women%27s_March_on_Washington_%2832593123745%29.jpg"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_womens-march-on-washington.jpg 480w, /empathetic-vs-skeptical-moderates/md_womens-march-on-washington.jpg 768w, /empathetic-vs-skeptical-moderates/lg_womens-march-on-washington.jpg 992w, /empathetic-vs-skeptical-moderates/xl_womens-march-on-washington.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_womens-march-on-washington.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_womens-march-on-washington.jpg"  src="/empathetic-vs-skeptical-moderates/womens-march-on-washington.jpg" alt="" loading="lazy" width="1280" height="853" /></div></a></p><h2 id="What-is-a-moderate">What is a <a href="https://en.wikipedia.org/wiki/moderate">moderate</a>?<a class="header-anchor" href="#What-is-a-moderate">§</a></h2><p>A <em>moderate</em> is a person whose opinions are not extreme, whose beliefs fall in the middle between any two sides.</p><p>Often, moderates are those who have the most privilege, who benefit most from the status quo, and who are the most tolerated.</p><p>On the flip side, being a moderate is not a applicable strategy for those who suffer from the status quo, who are part of marginalized communities, and who are actively intolerated.</p><p>However, I do believe being a moderate is a powerful strategy when used as a bridge to connect diversity. Whether I think a moderate is good or bad depends on whether they are connecting or disconnecting other sides.</p><h2 id="Good-moderates-are-empathetic-of-diverse-beliefs">Good moderates are empathetic of diverse beliefs<a class="header-anchor" href="#Good-moderates-are-empathetic-of-diverse-beliefs">§</a></h2><p>If you’re able to empathize with beliefs from diverse sides, yay!</p><p>Being empathetic means listening to another person or group’s lived experience and standing in solidarity with that experience as being valid.</p><p><a href="https://www.mprnews.org/story/2016/07/14/castile-demonstrations-white-allies"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_childrens-march.jpg 480w, /empathetic-vs-skeptical-moderates/md_childrens-march.jpg 768w, /empathetic-vs-skeptical-moderates/lg_childrens-march.jpg 992w, /empathetic-vs-skeptical-moderates/xl_childrens-march.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_childrens-march.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_childrens-march.jpg"  src="/empathetic-vs-skeptical-moderates/childrens-march.jpg" alt="" loading="lazy" width="1400" height="934" /></div></a></p><h2 id="Bad-moderates-are-skeptical-of-diverse-beliefs">Bad moderates are skeptical of diverse beliefs<a class="header-anchor" href="#Bad-moderates-are-skeptical-of-diverse-beliefs">§</a></h2><p>If you’re skepitcal of beliefs from diverse sides, boo!</p><p>Being skeptical means you judge the experience of another person or group.</p><p>Martin Luther King, Jr. had some words to say in a <a href="https://www.africa.upenn.edu/Articles_Gen/Letter_Birmingham.html">“Letter from a Birmingham Jail”</a> (16 April 1963):</p><blockquote><p>First, I must confess that over the last few years I have been gravely disappointed with the white moderate. I have almost reached the regrettable conclusion that the Negro’s great stumbling block in the stride toward freedom is not the White Citizen’s Council-er or the Ku Klux Klanner, but the white moderate who is more devoted to “order” than to justice; who prefers a negative peace which is the absence of tension to a positive peace which is the presence of justice; who constantly says “I agree with you in the goal you seek, but I can’t agree with your methods of direct action;” who paternalistically feels he can set the timetable for another man’s freedom; who lives by the myth of time and who constantly advises the Negro to wait until a “more convenient season”. Shallow understanding from people of goodwill is more frustrating than absolute misunderstanding from people of ill will. Lukewarm acceptance is much more bewildering than outright rejection.</p></blockquote><p>If you’re <a href="https://www.nytimes.com/2017/09/01/opinion/civil-rights-protest-resistance.html">waiting for the perfect protest</a>, please consider that everyone wants to do their best, but only those with privilege have the choice to opt-out of social justice.</p><p><a href="https://twitter.com/theferocity/status/909071302698393600"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_hello-somebody.jpg 480w, /empathetic-vs-skeptical-moderates/md_hello-somebody.jpg 768w, /empathetic-vs-skeptical-moderates/lg_hello-somebody.jpg 992w, /empathetic-vs-skeptical-moderates/xl_hello-somebody.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_hello-somebody.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_hello-somebody.jpg"  src="/empathetic-vs-skeptical-moderates/hello-somebody.jpg" alt="" loading="lazy" width="960" height="960" /></div></a></p><h3 id="Case-study-NFL-kneels">Case study: NFL kneels<a class="header-anchor" href="#Case-study-NFL-kneels">§</a></h3><p>Some time ago, a football player <a href="https://en.wikipedia.org/wiki/Colin_Kaepernick">Colin Kaepernick</a> started a movement by kneeling, instead of standing, during the national anthem.</p><p><a href="http://time.com/top-100-photos-of-2017/"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_national-anthem.jpg 480w, /empathetic-vs-skeptical-moderates/md_national-anthem.jpg 768w, /empathetic-vs-skeptical-moderates/lg_national-anthem.jpg 992w, /empathetic-vs-skeptical-moderates/xl_national-anthem.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_national-anthem.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_national-anthem.jpg"  src="/empathetic-vs-skeptical-moderates/national-anthem.jpg" alt="" loading="lazy" width="1536" height="1000" /></div></a></p><p>In an interview after the game, Kaepernick gave a clear meaning to his unique expression:</p><blockquote><p>I am not going to stand up to show pride in a flag for a country that oppresses black people and people of color. To me, this is bigger than football and it would be selfish on my part to look the other way. There are bodies in the street and people getting paid leave and getting away with murder.</p></blockquote><p>From then on, game after game, this unique expression of kneeling was used in public events to remind the audience about the message of institutional violence against black people.</p><p>As someone seeing this happen, we’re not being asked to agree or disagree, we’re being reminded of another group’s experiences. The kneeling is a reminder, lest we forget, that the public system is unjust for some people.</p><p>Yet, in my extended network I became witness to skepticism, with articles such as <a href="https://www.usatoday.com/story/opinion/2017/09/26/fl-stop-self-destructive-grandstanding-and-just-play-football-james-robbins-column/701472001/">“Quit grandstanding with NFL protests and get back to football”</a>.</p><blockquote><p>Sports used to be fun, but these protests are divisive and pointless. Can’t we just play ball?</p></blockquote><p>Rather than being empathetic with another group’s experience, I noticed <a href="https://en.wikipedia.org/wiki/Gaslighting">gaslighting</a>, to the point of <a href="https://johnpavlovitz.com/2017/09/26/protests-arent-flag-anthem-military-know/">deflecting the message to be about the flag, the anthem, or the military</a>.</p><p>But not all (white) moderates try to silence stories about oppression, some use their own voice to amplify the messages:</p><div class="twitter-wrapper"><blockquote class="twitter-tweet"><a href="https://twitter.com/Kaepernick7/status/917955545197314048"></a></blockquote></div><script async defer src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><h2 id="Moderate-strategy-be-a-bridge">Moderate strategy: be a bridge<a class="header-anchor" href="#Moderate-strategy-be-a-bridge">§</a></h2><p>As <a href="https://en.wikipedia.org/wiki/The_personal_is_political">the personal is political</a>, being a moderate with empathy is where we have an opportunity to affect positive social change, using our day-to-day interactions amongst our friends, family, co-workers, and strangers to be a bridge.</p><p>For example, here’s my friend <a href="http://dominictarr.com">Dominic Tarr</a> working to bridge the gap between conflicting sides during <a href="https://viewer.scuttlebot.io/%25dRuGqDTklt1VqHOEcdJkwgbUrMyELenSCscSbGz%2B8KQ%3D.sha256">a tense conversation on Scuttlebutt</a>:</p><blockquote><p>@marina this isn’t just some random dickwad, this dickwad is @mixmix’s dad. That is why I think it’s worth mediating, for better or worse, he’s basically family. @substack is addressing future ssb members, but I’m interesting in addressing the people who are <em>currently here</em>. Because one time <em>you</em> told me that one of the challenges facing feminism is that the default response my men is to be skeptical of whatever a woman says, but believe things that other men say. This was surprising at the time, but I soon realized you where right. So you told me that it wasn’t enough for women to argue for feminism - men like me had to as well, because other men would actually listen to me. That is what I’m trying to do here.<br><br /><br>@bob as you can see, my friends are a bit upset by this, and now you are connected to and socialising in the same space with them. I can certainly vouch that they are highly intelligent and there are many reasons they believe what they do. Could you please take that into consideration? they are not “some segments of some societies” they are a segment of <em>this</em> society.<br><br /><br>I think it’s worth bridging between these viewpoints, because I realized that my own views have changed significantly over the years. If my 18 year old self came on here and what I believed at the time, I’d have gotten blasted by current @substack too, and that would be fair enough, but that probably would have galvanized my views not changed them. So I believe that if my views can change, other’s views can change too.</p></blockquote><p>To be a good moderate is to listen, to give a voice to those who aren’t being heard (or can’t be present), to be a connecting link between diverse sides, to support a <a href="https://en.wikipedia.org/wiki/Diversity_of_tactics">diversity of tactics</a> brought by each participant given their understanding of the local situation.</p><h2 id="Moderate-strategy-be-an-ally">Moderate strategy: be an ally<a class="header-anchor" href="#Moderate-strategy-be-an-ally">§</a></h2><p>There are social justice movements happening around us, <em>be an ally</em>!</p><p>As a person with privilege, it’s easy to join a movement with good intentions, but have negative impact. notice how much attention you give or take. check if you are supporting or co-opt-ing.</p><p>Give attention to others! Try not to place yourself at the center of attention, unless as a stepping stone to pass the microphone.</p><p>Listen! A good practice is to notice how much you’re listening, versus how much you’re talking.</p><p>Ask how you can help! But <a href="https://english.emmaclit.com/2017/05/20/you-shouldve-asked/">don’t expect others to carry your mental load</a>.</p><p><a href="/empathetic-vs-skeptical-moderates/how-to-be-an-ally.jpg"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_how-to-be-an-ally.jpg 480w, /empathetic-vs-skeptical-moderates/md_how-to-be-an-ally.jpg 768w, /empathetic-vs-skeptical-moderates/lg_how-to-be-an-ally.jpg 992w, /empathetic-vs-skeptical-moderates/xl_how-to-be-an-ally.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_how-to-be-an-ally.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_how-to-be-an-ally.jpg"  src="/empathetic-vs-skeptical-moderates/how-to-be-an-ally.jpg" alt="" loading="lazy" width="980" height="700" /></div></a></p><h2 id="Caveat-The-paradox-of-tolerance">Caveat: The paradox of tolerance<a class="header-anchor" href="#Caveat-The-paradox-of-tolerance">§</a></h2><p>There’s a caveat with empathy, known as the <a href="https://en.wikipedia.org/wiki/Paradox_of_tolerance">“paradox of tolerance”</a></p><blockquote><p>If tolerance is inclusive of the intolerant, than intolerance will ultimately dominate, eliminating the tolerant and the practice of tolerance with them.</p></blockquote><p>There are small minorities of people who will use your empathy against you: <a href="https://en.wikipedia.org/wiki/Fascism">fascists</a>, racists, and other bigoted people.</p><p>Instead of trying to be seem as “reasonable”, cultivate beliefs.</p><blockquote><p>Cultivate believing victims, who you have been enculturated to believe “probably deserved it”.</p><p>Believe Black men–who are not super predators, but humans who are more likely than not to be victims.</p><p>Believe Native women–who are not costumes or the past, but both most likely to be harmed by any man, and still here, working for justice for EVERYONE.</p><p>Believe Black women–actually believe them, don’t just use them as a hashtag.</p><p>Believe Native men, who are probably the most likely to be killed by police.</p><p>Believe trans people. Believe queer people. Believe ace people when they tell you they are queer.</p><p>Believe disabled people, who are whole entire humans, no matter what we have been socialized to think.</p><p>Believe children.</p><p>Believe victims.</p><p>Stop believing the police. Stop believing men over women, whites over everyone, straight people over queer people, abled people above all.</p><p>Start tearing down injustice, and do that by tearing down the injustices inside you. Your assumptions.</p><p>Stop giving power to the warlords of hatred, by believing them that only their good opinion will set the rest of us free.</p></blockquote><ul><li><a href="https://archive.is/emblH">@cricketcrocker</a></li></ul><p><a href="https://www.brasscomics.com/crackers/?comic=where-you-stand"><div class="image-wrapper"><img srcset="/empathetic-vs-skeptical-moderates/sm_where-you-stand.jpg 480w, /empathetic-vs-skeptical-moderates/md_where-you-stand.jpg 768w, /empathetic-vs-skeptical-moderates/lg_where-you-stand.jpg 992w, /empathetic-vs-skeptical-moderates/xl_where-you-stand.jpg 1280w, /empathetic-vs-skeptical-moderates/2xl_where-you-stand.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/empathetic-vs-skeptical-moderates/lg_where-you-stand.jpg"  src="/empathetic-vs-skeptical-moderates/where-you-stand.jpg" alt="" loading="lazy" width="980" height="700" /></div></a></p><h2 id="We’re-all-in-this-together">We’re all in this together<a class="header-anchor" href="#We’re-all-in-this-together">§</a></h2><p><a href="https://blog.mikey.nz/being-charitable/">Be charitable</a> to everyone you come across.</p><p>Listen and believe the stories of marginalized people.</p><p>Understand that sometimes being empathetic means stepping outside <a href="https://blog.mikey.nz/safe-spaces-not-comfort-zones/">your comfort zone</a>.</p><p>Help people (especially those with privilege) to be more empathetic of others (especially those without privilege).</p><p>Help people get past labels which are different to them and thinking about others as humans similar to them.</p>]]></content>
    
    
    <summary type="html">In the last year, I’ve noticed two seas of (white) moderates: one as a valuable bridge, another as an infection of deception.</summary>
    
    
    <content src="http://blog.mikey.nz/empathetic-vs-skeptical-moderates/national-anthem.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Openers, Closers, and Support</title>
    <link href="http://blog.mikey.nz/openers-closers-and-support/"/>
    <id>http://blog.mikey.nz/openers-closers-and-support/</id>
    <published>2018-04-10T06:19:30.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>Once upon a team, we had a reflection: it’s unfair to expect everyone to do the same work equally (homogeneity), instead the total work is spread across many complementary roles (heterogeneity).</p><span id="more"></span><p>To help us towards this understanding, Greg shared the typical formation of a Counter Strike team:</p><ul><li><strong>Openers</strong>: those who explore new opportunities</li><li><strong>Closers</strong>: those who exploit existing opportunities</li><li><strong>Supports</strong>: those who provide supplementary help to openers or closers</li></ul><p><a href="https://www.reddit.com/r/ImaginaryMindscapes/comments/16lumyb/now_how_do_i_get_down_by_huleeb/"><div class="image-wrapper"><img srcset="/openers-closers-and-support/sm_now-how-do-i-get-down-by-huleeb.jpg 480w, /openers-closers-and-support/md_now-how-do-i-get-down-by-huleeb.jpg 768w, /openers-closers-and-support/lg_now-how-do-i-get-down-by-huleeb.jpg 992w, /openers-closers-and-support/xl_now-how-do-i-get-down-by-huleeb.jpg 1280w, /openers-closers-and-support/2xl_now-how-do-i-get-down-by-huleeb.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/openers-closers-and-support/lg_now-how-do-i-get-down-by-huleeb.jpg"  src="/openers-closers-and-support/now-how-do-i-get-down-by-huleeb.jpg" alt="" loading="lazy" width="1536" height="1920" /></div></a></p><h2 id="Roles">Roles<a class="header-anchor" href="#Roles">§</a></h2><h3 id="Openers">Openers<a class="header-anchor" href="#Openers">§</a></h3><p>Openers love to explore. Give them an unknown territory and they can come back with a map of the explored area.</p><h3 id="Closers">Closers<a class="header-anchor" href="#Closers">§</a></h3><p>Closers love to grind. Give them a map of an explored area and they can come up with creative solutions to problems that need solving.</p><h3 id="Supports">Supports<a class="header-anchor" href="#Supports">§</a></h3><p>Supports grease the wheels to make sure everything moves smoothly. This may be mentorship, coaching, peer reviews, etc.</p><h2 id="Me-For-Example">Me: For Example<a class="header-anchor" href="#Me-For-Example">§</a></h2><p>For me, I’m a great opener. I love doing proof-of-concepts! I love starting new projects. I love finding undiscovered gaps within spaces.</p><p>But I’m terrible at making things <em>great</em>. I only get so far as <em>good enough</em> and then I want to move on to the next exciting problem to solve.</p><p><a href="https://www.reddit.com/r/ImaginaryMindscapes/comments/zkrxyc/the_explorer_with_a_plan_magnus_moon_digital/"><div class="image-wrapper"><img srcset="/openers-closers-and-support/sm_magnus-moon-the-explorer-with-a-plan.jpg 480w, /openers-closers-and-support/md_magnus-moon-the-explorer-with-a-plan.jpg 768w, /openers-closers-and-support/lg_magnus-moon-the-explorer-with-a-plan.jpg 992w, /openers-closers-and-support/xl_magnus-moon-the-explorer-with-a-plan.jpg 1280w, /openers-closers-and-support/2xl_magnus-moon-the-explorer-with-a-plan.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/openers-closers-and-support/lg_magnus-moon-the-explorer-with-a-plan.jpg"  src="/openers-closers-and-support/magnus-moon-the-explorer-with-a-plan.jpg" alt="" loading="lazy" width="1536" height="2164" /></div></a></p><p>As an opener, I’m often seen as a leader. Our culture usually celebrates openers as the heros: the first to discover something, the first to explore somewhere, the first to whatever.</p><p>Look, if everyone was like me, if everyone was an opener, we’d have a bad time. Openers can only achieve great work when they work alongside closers. In my mind, the world should celebrate closers more, I sure need you!</p><h2 id="Team-Work">Team Work<a class="header-anchor" href="#Team-Work">§</a></h2><p>With this mindset, it’s unfair to expect someone who is a better closer to start a project from scratch. It’s also unfair to expect someone who is a better opener to do the long-tail work on delivering a project. And so on.</p><p>To generalize this mindset, everyone has an abundance of skills to offer. How do we match complementary skills together to advance our shared mission? How do we provide the support everyone needs to be their best?</p><p><a href="https://www.artstation.com/artwork/5vdNDz"><div class="image-wrapper"><img srcset="/openers-closers-and-support/sm_tiberius-ciucinciu-foraging-wild-edibles.jpg 480w, /openers-closers-and-support/md_tiberius-ciucinciu-foraging-wild-edibles.jpg 768w, /openers-closers-and-support/lg_tiberius-ciucinciu-foraging-wild-edibles.jpg 992w, /openers-closers-and-support/xl_tiberius-ciucinciu-foraging-wild-edibles.jpg 1280w, /openers-closers-and-support/2xl_tiberius-ciucinciu-foraging-wild-edibles.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/openers-closers-and-support/lg_tiberius-ciucinciu-foraging-wild-edibles.jpg"  src="/openers-closers-and-support/tiberius-ciucinciu-foraging-wild-edibles.jpg" alt="" loading="lazy" width="1536" height="2150" /></div></a></p><p>So, when I arrive at a project, I’m most interested in asking the questions:</p><ul><li>Who is available for doing work here?</li><li>What role do you want to embody?</li><li>What support do you need to be at your best?</li><li>How do you want to be held accountable for your work?</li></ul><p>Also sometimes, the role someone wants to embody might not be the role they embody best.</p><p>Team work is an iterative and collaborative process.</p><p><a href="https://www.reddit.com/r/ImaginaryMindscapes/comments/uro8wt/surreal_sunset/"><div class="image-wrapper"><img srcset="/openers-closers-and-support/sm_alternateartreality-surreal-sunset.jpg 480w, /openers-closers-and-support/md_alternateartreality-surreal-sunset.jpg 768w, /openers-closers-and-support/lg_alternateartreality-surreal-sunset.jpg 992w, /openers-closers-and-support/xl_alternateartreality-surreal-sunset.jpg 1280w, /openers-closers-and-support/2xl_alternateartreality-surreal-sunset.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/openers-closers-and-support/lg_alternateartreality-surreal-sunset.jpg"  src="/openers-closers-and-support/alternateartreality-surreal-sunset.jpg" alt="" loading="lazy" width="1536" height="1577" /></div></a></p>]]></content>
    
    
    <summary type="html">Once upon a team, we had a reflection: it’s unfair to expect everyone to do the same work equally (homogeneity), instead the total work is spread across many complementary roles (heterogeneity).</summary>
    
    
    <content src="http://blog.mikey.nz/openers-closers-and-support/now-how-do-i-get-down-by-huleeb.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Safe spaces not comfort zones</title>
    <link href="http://blog.mikey.nz/safe-spaces-not-comfort-zones/"/>
    <id>http://blog.mikey.nz/safe-spaces-not-comfort-zones/</id>
    <published>2018-03-25T23:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<p>Some months ago, my work family Root Systems had a cultural problem:</p><p>Our so-called <em>safe space</em> was really a <em>comfort zone</em>!</p><span id="more"></span><blockquote><p>caution: my perspectives on safety and comfort are <strong>privileged</strong>.</p></blockquote><p><a href="https://www.artstation.com/artwork/O6oxv"><div class="image-wrapper"><img srcset="/safe-spaces-not-comfort-zones/sm_safe-nostalgia.jpg 480w, /safe-spaces-not-comfort-zones/md_safe-nostalgia.jpg 768w, /safe-spaces-not-comfort-zones/lg_safe-nostalgia.jpg 992w, /safe-spaces-not-comfort-zones/xl_safe-nostalgia.jpg 1280w, /safe-spaces-not-comfort-zones/2xl_safe-nostalgia.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/safe-spaces-not-comfort-zones/lg_safe-nostalgia.jpg"  src="/safe-spaces-not-comfort-zones/safe-nostalgia.jpg" alt="" loading="lazy" width="1536" height="864" /></div></a></p><h2 id="What-is-a-comfort-zone">What is a comfort zone?<a class="header-anchor" href="#What-is-a-comfort-zone">§</a></h2><p>A comfort zone is where you are free from any _dis_comfort.</p><p>A comfort zone is where you can avoid fear, uncertainty, doubt, stress, or situational anxiety.</p><p>From <a href="https://en.wikipedia.org/wiki/The_Prophet_%28book%29">The Prophet</a> (emphasis mine):</p><blockquote><p>And tell me, people of Orphalese, what have you in these houses?<br>And what is it you guard with fastened doors?<br>Have you peace, the quiet urge that reveals your power?<br>Have you remembrances, the glimmering arches that span the summits of the mind?<br>Have you beauty, that leads the heart from things fashioned of wood and stone to the holy mountain?<br>Tell me, have you these in your houses?<br>Or <strong>have you only comfort, and the lust for comfort, that stealthy thing that enters the house a guest, and then becomes a host, and then a master?</strong></p><br /><p>Ay, and it becomes a tamer, and with hook and scourge makes puppets of your larger desires.<br>Though its hands are silken, its heart is of iron.<br>It lulls you to sleep only to stand by your bed and jeer at the dignity of the flesh.<br>It makes mock of your sound senses, and lays them in thistledown like fragile vessels.<br><strong>Verily the lust for comfort murders the passion of the soul, and then walks grinning in the funeral.</strong></p></blockquote><p><a href="https://www.pinterest.nz/pin/200480620889780005/"><div class="image-wrapper"><img srcset="/safe-spaces-not-comfort-zones/sm_comfort-zone.jpg 480w, /safe-spaces-not-comfort-zones/md_comfort-zone.jpg 768w, /safe-spaces-not-comfort-zones/lg_comfort-zone.jpg 992w, /safe-spaces-not-comfort-zones/xl_comfort-zone.jpg 1280w, /safe-spaces-not-comfort-zones/2xl_comfort-zone.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/safe-spaces-not-comfort-zones/lg_comfort-zone.jpg"  src="/safe-spaces-not-comfort-zones/comfort-zone.jpg" alt="" loading="lazy" width="564" height="574" /></div></a></p><h2 id="What-is-a-safe-space">What is a safe space?<a class="header-anchor" href="#What-is-a-safe-space">§</a></h2><p>A <a href="https://en.wikipedia.org/wiki/Safe-space">safe space</a> is a space where violence, harassment, or hate speech against marginalized groups is not tolerated.</p><blockquote><p>A place where anyone can relax and be fully self-expressed, without fear of being made to feel uncomfortable, unwelcome or challenged on account of biological sex, race/ethnicity, sexual orientation, gender identity or expression, cultural background, age, or physical or mental ability; a place where the rules guard each person’s self-respect, dignity and feelings and strongly encourage everyone to respect others. - <a href="http://www.advocatesforyouth.org/index.php?option=com_content&amp;task=view&amp;id=607&amp;Itemid=177">Advocates for Youth</a></p></blockquote><div style="text-align: center">  <a href="https://en.wikipedia.org/wiki/Safe-space">    <div class="image-wrapper"><img src="/safe-spaces-not-comfort-zones/safe-ally.svg" alt="safe ally" height="200px" / loading="lazy" /></div>  </a></div><p>A safe space is where you feel seen, where you feel heard, where you feel loved, as you are.</p><h2 id="Safety-not-comfort">Safety not comfort<a class="header-anchor" href="#Safety-not-comfort">§</a></h2><p>A safe space is not always a comfort zone!</p><p>A safe space is where you’re safe to be vulnerable, you’re encouraged to be your authentic self, you are supported for being you.</p><p>Even in the safest of spaces, people make mistakes, situations may become tense, conflicts may arise, work may be tough, it’s okay!</p><p>A safe space does not guarantee you will be free from unintended discomfort based on real challenges, but you will be free to be yourself to face those challenges!</p><p><a href="https://www.artstation.com/artwork/g6Bem"><div class="image-wrapper"><img srcset="/safe-spaces-not-comfort-zones/sm_safe-growing-rites.jpg 480w, /safe-spaces-not-comfort-zones/md_safe-growing-rites.jpg 768w, /safe-spaces-not-comfort-zones/lg_safe-growing-rites.jpg 992w, /safe-spaces-not-comfort-zones/xl_safe-growing-rites.jpg 1280w, /safe-spaces-not-comfort-zones/2xl_safe-growing-rites.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/safe-spaces-not-comfort-zones/lg_safe-growing-rites.jpg"  src="/safe-spaces-not-comfort-zones/safe-growing-rites.jpg" alt="" loading="lazy" width="1200" height="881" /></div></a></p><h2 id="Safe-for-who">Safe for who?<a class="header-anchor" href="#Safe-for-who">§</a></h2><p>I notice that there is a meme floating around regarding safe spaces, with a small baby of truth amongst the dirty bathwater.</p><p>Originally, safe spaces were created as a way to protect marginalized groups. This is a great idea!</p><p>However, I notice how sometimes “safe spaces” are now used as a way to punish other groups, often for mistakes due to ignorance not malice.</p><p>Extrapolated beyond reason into an infectious narrative, this becomes “political correctness gone too far”.</p><p><div class="image-wrapper"><img srcset="/safe-spaces-not-comfort-zones/sm_safe-maga-police.jpg 480w, /safe-spaces-not-comfort-zones/md_safe-maga-police.jpg 768w, /safe-spaces-not-comfort-zones/lg_safe-maga-police.jpg 992w, /safe-spaces-not-comfort-zones/xl_safe-maga-police.jpg 1280w, /safe-spaces-not-comfort-zones/2xl_safe-maga-police.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/safe-spaces-not-comfort-zones/lg_safe-maga-police.jpg"  src="/safe-spaces-not-comfort-zones/safe-maga-police.jpg" alt="" loading="lazy" width="720" height="478" /></div></p><p>But amongst the unreason, there is a small nugget of truth that people aren’t able to be themselves.</p><p>People don’t feel safe when they think they will be attacked for being themselves.</p><p>Yes, this is most important for marginalized groups, where default society is not a safe space for them to be themselves.</p><p>This is also important for everyone, we all deserve to be our best authentic selves, even when we make mistakes.</p><h2 id="How-to-welcome-discomfort">How to welcome discomfort<a class="header-anchor" href="#How-to-welcome-discomfort">§</a></h2><p>Our comfort is a privilege, not an entitlement.</p><p>When we stay within our comfort zone, we enable our weaknesses at the expense of hinder personal growth.</p><p>Paradoxically, to become a better person we want to choose to be uncomfortable, to face our fears directly.</p><p>To face our fears, we should be grounded in a supportive network of strength and care.</p><p>We won’t find our best self in our comfort zone, it will be found in the (sometimes uncomfortable) safe space!</p><h2 id="Notable-mentions">Notable mentions<a class="header-anchor" href="#Notable-mentions">§</a></h2><ul><li><a href="https://medium.com/enspiral-tales/4-things-that-struck-me-after-visiting-political-spaces-in-14-us-cities-c1dceb1e8cb4">#3 of “4 Things That Struck Me After Visiting Political Spaces in 14 US Cities”</a></li><li><a href="https://viewer.scuttlebot.io/%25KfyWqTDLSWu94KtE2buU2eoKnrBajHGqw7zbphVc0Rs%3D.sha256">%KfyWqTDLSWu94KtE2buU2eoKnrBajHGqw7zbphVc0Rs=.sha256</a>)</li></ul>]]></content>
    
    
    <summary type="html">Some months ago, my work family Root Systems had a cultural problem:

Our so-called safe space was really a comfort zone!</summary>
    
    
    <content src="http://blog.mikey.nz/safe-spaces-not-comfort-zones/safe-nostalgia.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Being charitable</title>
    <link href="http://blog.mikey.nz/being-charitable/"/>
    <id>http://blog.mikey.nz/being-charitable/</id>
    <published>2018-03-13T09:42:19.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>My co-worker Greg introduced me to a useful way to reflect on our interpretations: whether or not we are <em>being charitable</em>.</p><span id="more"></span><p>(Art by <a href="http://victormosquera.tumblr.com/">Victor Mosquera</a>)</p><p><a href="http://victormosquera.tumblr.com/"><div class="image-wrapper"><img srcset="/being-charitable/sm_charitable-opening.jpg 480w, /being-charitable/md_charitable-opening.jpg 768w, /being-charitable/lg_charitable-opening.jpg 992w, /being-charitable/xl_charitable-opening.jpg 1280w, /being-charitable/2xl_charitable-opening.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/being-charitable/lg_charitable-opening.jpg"  src="/being-charitable/charitable-opening.jpg" alt="" loading="lazy" width="1080" height="1080" /></div></a></p><h2 id="How-to-be-charitable">How to be charitable<a class="header-anchor" href="#How-to-be-charitable">§</a></h2><p>The background for this terminology is in philosophy: the <a href="https://en.wikipedia.org/wiki/Principle_of_charity">Principle of Charity</a>, which says that before you argue <em>against</em> someone, you should build up their argument to be as <strong>strong</strong> as possible.</p><p>Applying this to a social context: before we give someone negative feedback, we should assume the best possible intentions.</p><p>We should construct the most favorable opinion of someone until proven otherwise, to regard them as <em>innocent until proven guilty</em>, to <em>give them the benefit of the doubt</em>.</p><p><a href="http://victormosquera.tumblr.com/"><div class="image-wrapper"><img srcset="/being-charitable/sm_charitable-review.jpg 480w, /being-charitable/md_charitable-review.jpg 768w, /being-charitable/lg_charitable-review.jpg 992w, /being-charitable/xl_charitable-review.jpg 1280w, /being-charitable/2xl_charitable-review.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/being-charitable/lg_charitable-review.jpg"  src="/being-charitable/charitable-review.jpg" alt="" loading="lazy" width="1080" height="1080" /></div></a></p><h3 id="The-prime-directive">The prime directive<a class="header-anchor" href="#The-prime-directive">§</a></h3><p>In the context of agile retrospectives, we see this concept as <a href="http://www.retrospectivewiki.org/index.php?title=The_Prime_Directive">The Prime Directive</a>:</p><blockquote><p>Regardless of what we discover, we understand and truly believe that everyone did the best job they could, given what they knew at the time, their skills and abilities, the resources available, and the situation at hand.</p></blockquote><p>When we assume good faith, we are able to go beyond blaming any person involved and instead focus on the systems we are in.</p><p><a href="http://victormosquera.tumblr.com/"><div class="image-wrapper"><img srcset="/being-charitable/sm_charitable-composition.jpg 480w, /being-charitable/md_charitable-composition.jpg 768w, /being-charitable/lg_charitable-composition.jpg 992w, /being-charitable/xl_charitable-composition.jpg 1280w, /being-charitable/2xl_charitable-composition.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/being-charitable/lg_charitable-composition.jpg"  src="/being-charitable/charitable-composition.jpg" alt="" loading="lazy" width="1080" height="1080" /></div></a></p><h2 id="Signs-you-are-not-being-charitable">Signs you are not being charitable<a class="header-anchor" href="#Signs-you-are-not-being-charitable">§</a></h2><p>Sometimes we aren’t charitable. It happens, we’re human!</p><p>For me, I want to recognize when I’m not being charitable so I can learn from my mistakes.</p><p>Recently, Greg tapped me on the shoulder for support on something he was working on. After only giving a brief look at his work so far, I say something like “this is sloppy”. He calmly responds with “I would appreciate if you could be more charitable”. Oh, I realize he took some shortcuts because of some tough barriers to climb, the same barriers he was hoping I could help him with! Instead here I’m thoughtlessly being mean which is not helping him solve his problem.</p><p>So, you are probably not being charitable if</p><ul><li>you ignore the context behind what the person is saying</li><li>you give a quick negative interpretation without much thinking</li><li>you back up your interpretation with pre-existing tension or conflicts (as in, bringing your baggage with you)</li><li>you nitpick words or assume you would have a better interpretation with the right words (“if only you had said”)</li></ul><p><a href="http://victormosquera.tumblr.com/"><div class="image-wrapper"><img srcset="/being-charitable/sm_charitable-poking.jpg 480w, /being-charitable/md_charitable-poking.jpg 768w, /being-charitable/lg_charitable-poking.jpg 992w, /being-charitable/xl_charitable-poking.jpg 1280w, /being-charitable/2xl_charitable-poking.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/being-charitable/lg_charitable-poking.jpg"  src="/being-charitable/charitable-poking.jpg" alt="" loading="lazy" width="1080" height="1080" /></div></a></p><h2 id="Charity-loops">Charity loops<a class="header-anchor" href="#Charity-loops">§</a></h2><p>By being charitable, we are able to appreciate our common union rather than standalone difference.</p><p>When we expect the best of people and give them trust, they will feel empowered to be trustworthy and give us abundance.</p><p>When we are charitable to ourselves, we will be able to fulfill our own needs, without living on the assumption of what others expect.</p><p>Together, we can give gifts of a charitable interpretation.</p><p><a href="http://victormosquera.tumblr.com/"><div class="image-wrapper"><img srcset="/being-charitable/sm_charitable-sailing.jpg 480w, /being-charitable/md_charitable-sailing.jpg 768w, /being-charitable/lg_charitable-sailing.jpg 992w, /being-charitable/xl_charitable-sailing.jpg 1280w, /being-charitable/2xl_charitable-sailing.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/being-charitable/lg_charitable-sailing.jpg"  src="/being-charitable/charitable-sailing.jpg" alt="" loading="lazy" width="1080" height="1080" /></div></a></p>]]></content>
    
    
    <summary type="html">My co-worker Greg introduced me to a useful way to reflect on our interpretations: whether or not we are being charitable.</summary>
    
    
    <content src="http://blog.mikey.nz/being-charitable/charitable-opening.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>A reflection on mirrors</title>
    <link href="http://blog.mikey.nz/a-reflection-on-mirrors/"/>
    <id>http://blog.mikey.nz/a-reflection-on-mirrors/</id>
    <published>2018-02-24T21:29:41.174Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>A few months ago, my co-worker Juliet planted a seed in my mind:</p><blockquote><p>The best gift a friend can provide you is a mirror: a reflection of who you really are.</p></blockquote><p>I’ve been reflecting on who I really am, and the ways I receive more insight into who I really am.</p><span id="more"></span><h2 id="What’s-a-mirror">What’s a mirror?<a class="header-anchor" href="#What’s-a-mirror">§</a></h2><p>A mirror is something that reflects an image of you, so you can see yourself.</p><p>The best mirrors are those that reflect an accurate image, without too much distortion.</p><p>But any mirror that allows you to see yourself from a different perspective, even when biased, is useful!</p><h2 id="Seeing-yourself-in-the-mirror">Seeing yourself in the mirror<a class="header-anchor" href="#Seeing-yourself-in-the-mirror">§</a></h2><p>Mirrors allow us to see ourselves in a light that is beyond our own perspectives, intuitions, and narratives.</p><p>In these moments, when we have an open mind and see ourselves in a new light, we are able to transform.</p><h2 id="Who’s-that-in-the-mirror">Who’s that in the mirror?<a class="header-anchor" href="#Who’s-that-in-the-mirror">§</a></h2><p>Some years ago, I had a profound experience while seeing myself in a bathroom mirror.</p><p>I had found a sun dress and was wearing it for the first time at a party.</p><p>I went to the bathroom and looked at myself in the mirror.</p><p><div class="image-wrapper"><img srcset="/a-reflection-on-mirrors/sm_bathroom-mirror-selfie.jpg 480w, /a-reflection-on-mirrors/md_bathroom-mirror-selfie.jpg 768w, /a-reflection-on-mirrors/lg_bathroom-mirror-selfie.jpg 992w, /a-reflection-on-mirrors/xl_bathroom-mirror-selfie.jpg 1280w, /a-reflection-on-mirrors/2xl_bathroom-mirror-selfie.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/a-reflection-on-mirrors/lg_bathroom-mirror-selfie.jpg"  src="/a-reflection-on-mirrors/bathroom-mirror-selfie.jpg" alt="" loading="lazy" width="1200" height="1600" /></div></p><p>The image of me I saw in the mirror did not match the image I had for myself.</p><p>Initially I rejected the image I saw, the person I was seeing wasn’t me, I’m a <em>man</em>!</p><p>With time I came to accept my new image of me wearing a dress, which allowed me to construct a more inclusive identity that embodied both masculine and feminine.</p><h2 id="Using-other-people-as-a-mirror">Using other people as a mirror<a class="header-anchor" href="#Using-other-people-as-a-mirror">§</a></h2><p>Your friends are mirrors too!</p><p>Your friends each have an image of you, based on their experiences and reflections.</p><p>A friend can share direct feedback about their image of you.</p><p>By receiving another’s mental model of you, you can better understand who you really are and update your mental model accordingly.</p><p>We want our friends to provide an honest mirror, who we really are to them, not who we want to believe we are to them.</p><p>This gives us wider range of perspectives than we can have alone.</p><h3 id="Inacurrate-mirrors">Inacurrate mirrors<a class="header-anchor" href="#Inacurrate-mirrors">§</a></h3><p>Sometimes an inaccurate mirror is helpful because it can stimulate a gut reaction about who you really are.</p><p>Even if it’s not an accurate picture of you, it can still be useful to see yourself in a different light!</p><h2 id="Using-interactions-as-a-mirror">Using interactions as a mirror<a class="header-anchor" href="#Using-interactions-as-a-mirror">§</a></h2><p>Every interaction you have is a mirror!</p><p>You might be able to see yourself through a first impression with a stranger.</p><p>Or maybe someone asks you something, your initial reaction is a good view into your intuition!</p><p>We are surrounded by mirrors… have you seen yourself lately?</p>]]></content>
    
    
    <summary type="html">A few months ago, my co-worker Juliet planted a seed in my mind:

&gt; The best gift a friend can provide you is a mirror: a reflection of who you really are.

I’ve been reflecting on who I really am, and the ways I receive more insight into who I really am.</summary>
    
    
    <content src="http://blog.mikey.nz/a-reflection-on-mirrors/bathroom-mirror-selfie.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Portable solar v1</title>
    <link href="http://blog.mikey.nz/portable-solar-v1/"/>
    <id>http://blog.mikey.nz/portable-solar-v1/</id>
    <published>2018-01-22T11:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Beginnings">Beginnings<a class="header-anchor" href="#Beginnings">§</a></h2><p>As I need power for my <a href="/pixels-for-the-pixel-god">portable rainbows</a>, and I think I over-discharged the lithium battery I used over New Years’ (oops!)…</p><p>I got a new portable 120w solar panel and a used 85Ah flooded lead acid battery, gonna make a 5v usb charging station to bring to Kiwiburn in 1.5 weeks! I’m keen for this to be my first step into learning about practical solar and battery power, even if I live day-to-day on-grid.</p><p>Here’s me testing what I have in the sun this morning:</p><p><div class="image-wrapper"><img srcset="/portable-solar-v1/sm_solar-stairs.jpg 480w, /portable-solar-v1/md_solar-stairs.jpg 768w, /portable-solar-v1/lg_solar-stairs.jpg 992w, /portable-solar-v1/xl_solar-stairs.jpg 1280w, /portable-solar-v1/2xl_solar-stairs.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v1/lg_solar-stairs.jpg"  src="/portable-solar-v1/solar-stairs.jpg" alt="" loading="lazy" width="900" height="1200" /></div></p><p>Worked with Piet last night at <a href="https://arthack.nz">Art~Hack</a> to understand how to control a battery’s discharge.</p><p><div class="image-wrapper"><img srcset="/portable-solar-v1/sm_solar-battery-discharge.jpg 480w, /portable-solar-v1/md_solar-battery-discharge.jpg 768w, /portable-solar-v1/lg_solar-battery-discharge.jpg 992w, /portable-solar-v1/xl_solar-battery-discharge.jpg 1280w, /portable-solar-v1/2xl_solar-battery-discharge.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v1/lg_solar-battery-discharge.jpg"  src="/portable-solar-v1/solar-battery-discharge.jpg" alt="" loading="lazy" width="1200" height="900" /></div></p><p>Then Ben informed us that solar charge controllers already do this, they will shut off the “load” output when the battery is low.</p><p>If so, making a 5v usb charging station should be really easy, just get a step down buck converter and convert 12v to 5v, then fan out this power to many usb connectors.</p><p>Stretch goal will be to make a nice display of the current power level, with maybe some playful messages as the power charges or discharges.</p><p>🌈 ☀</p><h2 id="Documentation">Documentation<a class="header-anchor" href="#Documentation">§</a></h2><p>Source: <a href="https://github.com/ahdinosaur/solarmonk"><code>ahdinosaur/solarmonk</code></a></p><h3 id="Components">Components<a class="header-anchor" href="#Components">§</a></h3><h4 id="Solar-panel-with-built-in-charge-controller">Solar panel (with built-in charge controller)<a class="header-anchor" href="#Solar-panel-with-built-in-charge-controller">§</a></h4><p><a href="http://maxray.com.au/folding-solar-panel/">120w maxray folding solar panel</a></p><ul><li>Max Power: 120W</li><li>Maximum Power Tolerance: ±3%</li><li>Open-Circuit Voltage/Voc（V): 17.5V</li><li>Short-Circuit Current/lsc（A): 9.42</li><li>Max Power Voltage/Vmp（V): 14.0</li><li>Max Power Current/lmp（A): 8.57</li><li>Power Spectications at STC: 1000w/㎡，AM1.5,CELL25℃</li><li>Max System Voltage（V) 1000</li><li>Max Over Current Protecting Rating（A): 15</li><li>Weight: 12.6kg</li></ul><h4 id="Battery">Battery<a class="header-anchor" href="#Battery">§</a></h4><p><a href="https://www.trademe.co.nz/Browse/Listing.aspx?id=1506593644">12v - 85Ah deep cycle flooded lead acid battery</a></p><blockquote><p><em>voltage must not drop below 11v</em></p></blockquote><p><a href="http://www.itacanet.org/eng/elec/battery/battery.pdf">Guide to lead-acid batteries</a></p><blockquote><p>fully charged is 12.6v to 12.8v</p></blockquote><table><thead><tr><th>State of Charge (approx.)</th><th>12 Volt Battery</th><th>Volts per Cell</th></tr></thead><tbody><tr><td>100%</td><td>12.70</td><td>2.12</td></tr><tr><td>90%</td><td>12.50</td><td>2.08</td></tr><tr><td>80%</td><td>12.42</td><td>2.07</td></tr><tr><td>70%</td><td>12.32</td><td>2.05</td></tr><tr><td>60%</td><td>12.20</td><td>2.03</td></tr><tr><td>50%</td><td>12.06</td><td>2.01</td></tr><tr><td>40%</td><td>11.90</td><td>1.98</td></tr><tr><td>30%</td><td>11.75</td><td>1.96</td></tr><tr><td>20%</td><td>11.58</td><td>1.93</td></tr><tr><td>10%</td><td>11.31</td><td>1.89</td></tr><tr><td>0</td><td>10.50</td><td>1.75</td></tr></tbody></table><h4 id="Battery-charge-reader">Battery charge reader<a class="header-anchor" href="#Battery-charge-reader">§</a></h4><ul><li>BTE14-04 <a href="http://betamcu.cn">betamcu.cn</a> Arduino Uno R3 Compatible</li><li>“DF Robot” LCD Keypad Shield</li><li>R1 resistor = 10k ohms</li><li>R2 resistor = 820 ohms</li><li>ceramic capacitor = 100nF</li><li><a href="https://en.wikipedia.org/wiki/Schottky_diode">schottky diode</a></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">12v  5v            5v</span><br><span class="line"> │    │             |</span><br><span class="line">R1    ⏄         ┌───┴───┐</span><br><span class="line"> │    │         │       │</span><br><span class="line"> ├────┼────┬────┤A1     │</span><br><span class="line"> │    │    │    │       │</span><br><span class="line">R2    ⏄    ═    │arduino│</span><br><span class="line"> │    │    │    └───────┘</span><br><span class="line"> ⏚    ⏚    ⏚</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="Battery-discharge-controller">Battery discharge controller<a class="header-anchor" href="#Battery-discharge-controller">§</a></h4><p>To ensure the battery is never over-discharged, we should cut off extra power when the battery reaches a low enough voltage.</p><h4 id="12v-to-5v-step-down-buck-converter">12v to 5v step-down (buck) converter<a class="header-anchor" href="#12v-to-5v-step-down-buck-converter">§</a></h4><p><a href="https://www.trademe.co.nz/Browse/Listing.aspx?id=1521227419">step down dc-dc 9A converter (constant volts or constant amps), 5~40v input &amp; 1.5~35v out</a></p><h3 id="Calculations">Calculations<a class="header-anchor" href="#Calculations">§</a></h3><h4 id="Resistive-divider"><a href="https://en.wikipedia.org/wiki/Voltage_divider#Resistive_divider">Resistive divider</a><a class="header-anchor" href="#Resistive-divider">§</a></h4><p>To measure the battery voltage on the microcontroller, we divide the voltage using resistors:</p><p>Given</p><ul><li>adc input impedence =  10k ohms<ul><li><a href="https://electronics.stackexchange.com/a/67172">https://electronics.stackexchange.com/a/67172</a></li></ul></li><li>adc reference voltage = 1.1V<ul><li><a href="https://www.arduino.cc/reference/en/language/functions/analog-io/analogreference/">https://www.arduino.cc/reference/en/language/functions/analog-io/analogreference/</a></li></ul></li><li>max battery voltage = 14V + wiggle room = ~15V</li></ul><p>So R1 = adc input impedence = 10k ohms</p><p>(want to be as high as possible to reduce current)</p><p>Calculate</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">R2 = R1 * (1 / ((Vin / Vout) - 1))</span><br><span class="line">   = Rinput_impedence * (1 / ((Vmax_bat / Vref) - 1))</span><br><span class="line">   = 10e3 * (1 / ((15 / 1.1) - 1))</span><br><span class="line">   = 791.37</span><br><span class="line">  ~= 820 ohms</span><br></pre></td></tr></table></figure><h2 id="Battery-charge-reading">Battery charge reading<a class="header-anchor" href="#Battery-charge-reading">§</a></h2><p>Piet let me borrow his power supply and multimeter so I could make sense of this.</p><p><div class="image-wrapper"><img srcset="/portable-solar-v1/sm_batter-charge-reader-2.jpg 480w, /portable-solar-v1/md_batter-charge-reader-2.jpg 768w, /portable-solar-v1/lg_batter-charge-reader-2.jpg 992w, /portable-solar-v1/xl_batter-charge-reader-2.jpg 1280w, /portable-solar-v1/2xl_batter-charge-reader-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v1/lg_batter-charge-reader-2.jpg"  src="/portable-solar-v1/batter-charge-reader-2.jpg" alt="" loading="lazy" width="900" height="1200" /></div></p><p>Got it working on a breadboard, then soldered to a protoboard. Threw up some code to run on the Arduino with the screen.</p><p><div class="image-wrapper"><img srcset="/portable-solar-v1/sm_batter-charge-reader.jpg 480w, /portable-solar-v1/md_batter-charge-reader.jpg 768w, /portable-solar-v1/lg_batter-charge-reader.jpg 992w, /portable-solar-v1/xl_batter-charge-reader.jpg 1280w, /portable-solar-v1/2xl_batter-charge-reader.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v1/lg_batter-charge-reader.jpg"  src="/portable-solar-v1/batter-charge-reader.jpg" alt="" loading="lazy" width="1200" height="900" /></div></p><p>There’s a problem where the voltage being read by the Arduino is quite a bit off, I hope at least it’s consistent so I can calibrate against it. I learned that this might could be improved by investing in a good reference voltage, oh well for meow.</p><p>I also learned that batteries have wildly different voltages when charging, at rest, and discharging. In the worst case i’ll be conservative, bring a multimeter to the festival, and manually ensure that we never discharge too much power.</p><p>Next up will be to put all the bits and bobs in an enclosure and at least get the 12v to 5v conversion working reliably, with 2.1mm barrel connector and usb outputs.</p><h2 id="ready-or-not-here-I-come">ready or not, here I come!<a class="header-anchor" href="#ready-or-not-here-I-come">§</a></h2><p>Spent the last few days detached from reality flowing like hot solder.</p><p>Now have:</p><ul><li>1x <a href="https://github.com/ahdinosaur/solarmonk/tree/4f3642644c616c7b21d5efa27d84254ad5430460">“solarmonk”</a> controller<ul><li>input is 12v with xt60 male connector</li><li>outputs<ul><li>5x 5v with 2.1mm dc female mount</li><li>2x 5v usb female connector</li></ul></li></ul></li><li>1x <a href="https://github.com/ahdinosaur/aburndance/tree/8e14a3e7a250569f8a8f7b0db3fe1ba77b426a84">“aburndance”</a> controller<ul><li>outputs for APA102 leds</li></ul></li><li>5x 7.5A 2.1mm male-to-male cables (of varying lengths from 2m to 15m) to re-inject power to the led strips</li><li>3x 25A xt60 male-to-female extensions (1x 8m and 2x 4m ish)</li></ul><p>(Apologies for using human gender terms to describe hardware connectors, is there a better alternative?)</p><p>At one point it had an arduino with a display showing the connected battery voltage and current amperage draw, <em>but</em> last night when I should have been sleeping, I connected everything up and it didn’t work. Without a load, everything checked out on the multimeter great, but then with a working load (the led strips), it buggered out. Since the buck converter does both constant voltage <em>and</em> constant current, I thought maybe I had set the constant current the wrong way. so I set it all the way to one side. But it still didn’t work, so I checked the voltage: 3v. When it was 5v without a load.</p><p>Then I made a terrible mistake, I increased the voltage, eventually to the point where I burned the led strips <em>and</em> the arduino. I stopped being silly and went to sleep. Today I checked everything with a clear head, luckily the most important part, the buck converter, was not affected by the damage. With some debugging, I realized the voltage had dropped because the constant current was set to the lowest setting. Now I <em>think</em> everything is working for real at 5v?</p><p>Here’s a picture so I can say, at some point something worked:</p><p><div class="image-wrapper"><img srcset="/portable-solar-v1/sm_at-some-point-something-worked.jpg 480w, /portable-solar-v1/md_at-some-point-something-worked.jpg 768w, /portable-solar-v1/lg_at-some-point-something-worked.jpg 992w, /portable-solar-v1/xl_at-some-point-something-worked.jpg 1280w, /portable-solar-v1/2xl_at-some-point-something-worked.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/portable-solar-v1/lg_at-some-point-something-worked.jpg"  src="/portable-solar-v1/at-some-point-something-worked.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Remains to be seen if anything will work on the paddock. ☀🔋🌈</p><p>I’ve learned so much! I’m learning how to do hardware in a way that is reliable and so doesn’t suck when you want to play with software.</p>]]></content>
    
    
    <summary type="html">A portable solar discharge controller</summary>
    
    
    <content src="http://blog.mikey.nz/portable-solar-v1/batter-charge-reader.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="solar" scheme="http://blog.mikey.nz/tags/solar/"/>
    
  </entry>
  
  <entry>
    <title>PIXELS FOR THE PIXEL GOD</title>
    <link href="http://blog.mikey.nz/pixels-for-the-pixel-god/"/>
    <id>http://blog.mikey.nz/pixels-for-the-pixel-god/</id>
    <published>2017-12-30T11:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>As a break from my other open source projects, have decided to start spending my <a href="https://arthack.nz">Art~Hack</a> evenings on my next (3rd?) generation of modular LED pixels that beat to music.</p><span id="more"></span><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796536263?h=36d25c00bf&background=1" data-title="(2015) Scrolling Rainbow"></div><h2 id="First-generation">First generation<a class="header-anchor" href="#First-generation">§</a></h2><p>First generation: <a href="https://github.com/ahdinosaur/beatpixels">beatpixels</a></p><ul><li>uses WS2801 pixels (pre-soldered to inject power every 2 or 4 meters?)</li><li>uses an Arduino Uno micro-controller to control the pixels using a custom message protocol over serial</li><li>uses an laptop running Pure Data to send messages to the Arduino</li><li>learning: it’s difficult to be expressive in the C language</li></ul><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_cloyne-pixel-board.jpg 480w, /pixels-for-the-pixel-god/md_cloyne-pixel-board.jpg 768w, /pixels-for-the-pixel-god/lg_cloyne-pixel-board.jpg 992w, /pixels-for-the-pixel-god/xl_cloyne-pixel-board.jpg 1280w, /pixels-for-the-pixel-god/2xl_cloyne-pixel-board.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_cloyne-pixel-board.jpg"  src="/pixels-for-the-pixel-god/cloyne-pixel-board.jpg" alt="" loading="lazy" width="704" height="960" /></div></p><h2 id="Second-generation">Second generation<a class="header-anchor" href="#Second-generation">§</a></h2><p>The second (and maybe second-second) generation: <a href="https://github.com/ahdinosaur/pixelbeat/tree/reactive">pixelbeat</a> / <a href="https://github.com/ahdinosaur/mood-light">mood-light</a>.</p><ul><li>used a BeagleBone Black / Green computer, later just used my laptop with a usb-to-ftdi-to-spi controller</li><li>used WS2012 / APA102C pixels, manually split strips to inject power every 2 meters<ul><li>friend Gordon (<a href="https://www.facebook.com/Fre3formD">Fre3formd</a>) made a custom 3d-printed enclosure for power injection connections to reduce failure during setup and teardown</li></ul></li><li>created and used module ecosystems around <a href="https://github.com/scijs/ndarray">ndarray</a>: <a href="https://github.com/livejs/ndpixels">ndpixels</a>, <a href="https://github.com/ahdinosaur/ndsamples">ndsamples</a></li><li>eventually learned to make modules be simple functions, like <a href="https://github.com/livejs/pixels-apa102">pixels-apa102</a>, in order to better collaborate with others like Matt</li><li>experimented with BeagleBone booting directly to an electron app as the window manager: <a href="https://github.com/ahdinosaur/boot-to-electron">boot-to-electron</a></li><li>learnings<ul><li>computer operating systems are fragile</li><li>JavaScript garbage collection is bad for real-time</li><li>BeagleBones don’t have enough juice to power a smooth audio-reactive visual set</li><li>manually injecting power into strips takes too much time to setup</li></ul></li></ul><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_art-hack-pixelbeat.jpg 480w, /pixels-for-the-pixel-god/md_art-hack-pixelbeat.jpg 768w, /pixels-for-the-pixel-god/lg_art-hack-pixelbeat.jpg 992w, /pixels-for-the-pixel-god/xl_art-hack-pixelbeat.jpg 1280w, /pixels-for-the-pixel-god/2xl_art-hack-pixelbeat.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_art-hack-pixelbeat.jpg"  src="/pixels-for-the-pixel-god/art-hack-pixelbeat.jpg" alt="" loading="lazy" width="720" height="960" /></div></p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_art-hack-pixelbeat-2.jpg 480w, /pixels-for-the-pixel-god/md_art-hack-pixelbeat-2.jpg 768w, /pixels-for-the-pixel-god/lg_art-hack-pixelbeat-2.jpg 992w, /pixels-for-the-pixel-god/xl_art-hack-pixelbeat-2.jpg 1280w, /pixels-for-the-pixel-god/2xl_art-hack-pixelbeat-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_art-hack-pixelbeat-2.jpg"  src="/pixels-for-the-pixel-god/art-hack-pixelbeat-2.jpg" alt="" loading="lazy" width="720" height="960" /></div></p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_mood-light.jpg 480w, /pixels-for-the-pixel-god/md_mood-light.jpg 768w, /pixels-for-the-pixel-god/lg_mood-light.jpg 992w, /pixels-for-the-pixel-god/xl_mood-light.jpg 1280w, /pixels-for-the-pixel-god/2xl_mood-light.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_mood-light.jpg"  src="/pixels-for-the-pixel-god/mood-light.jpg" alt="" loading="lazy" width="714" height="446" /></div></p><p>gigs:</p><ul><li><a href="https://twitter.com/MattMcKegg/status/713915311389421568">inky waves</a> (using Matt’s <a href="https://github.com/mmckegg/audio-splatter">audio-splatter</a>)</li><li><a href="https://www.youtube.com/watch?v=tehrxPaI4hk">campjs</a> (using Matt’s <a href="http://loopjs.com">Loop Drop</a>)</li><li>… a few others that I’m too lazy to find.</li></ul><h2 id="Third-generation">Third generation ???<a class="header-anchor" href="#Third-generation">§</a></h2><p>Third generation: <a href="https://github.com/ahdinosaur/pj">pj (pixel jockey)</a> !</p><ul><li>use WiFi-enabled micro-controller (<a href="http://esp32.net/">ESP32</a>) to control the pixels using <a href="http://openpixelcontrol.org">Open Pixel Control</a></li><li>create a desktop and mobile app to send messages to the micro-controller over WiFi</li><li>either have 4 meter strips, or find a way to get 4 meter strips pre-soldered for injection. use either separate AC-&gt;DC units per injection, or find a way to get clean connectors to a larger unit.</li><li>if at a gig and you want to minimize latency, have an option to connect over Ethernet</li><li>have clean enclosures and connectors, or don’t even bother wasting time</li></ul><h3 id="It-begins">It begins<a class="header-anchor" href="#It-begins">§</a></h3><p>As always, it begins with a <a href="https://github.com/ahdinosaur/rainbow-pixels">scrolling rainbow</a> 🌈</p><p>(Using an electron app to simulate the hardware in JavaScript before I commit to anything.)</p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_scrolling-rainbow-2.png 480w, /pixels-for-the-pixel-god/md_scrolling-rainbow-2.png 768w, /pixels-for-the-pixel-god/lg_scrolling-rainbow-2.png 992w, /pixels-for-the-pixel-god/xl_scrolling-rainbow-2.png 1280w, /pixels-for-the-pixel-god/2xl_scrolling-rainbow-2.png 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_scrolling-rainbow-2.png"  src="/pixels-for-the-pixel-god/scrolling-rainbow-2.png" alt="" loading="lazy" width="805" height="639" /></div></p><ul><li>published <a href="https://github.com/ahdinosaur/pull-opc">pull-opc@1</a> to send and receive Open Pixel Control messages using pull streams</li><li>published <a href="https://github.com/livejs/pixels-opc">pixels-opc@4</a> to send <a href="https://github.com/livejs/ndpixels">ndpixels</a></li><li>scaffolded a follower simulator using electron</li><li>follower broadcasts up over mdns</li><li>scaffold a leader that sends rainbow pixels to any up followers</li></ul><h2 id="LED-power-requirements-and-connectors">LED power requirements and connectors<a class="header-anchor" href="#LED-power-requirements-and-connectors">§</a></h2><p>From <a href="https://www.pololu.com/product/2554">Pololu’s page on the APA102C LEDS</a>:</p><blockquote><p>Each RGB LED draws approximately 50 mA when it is set to full brightness and powered at 5 V. This means that for every 30 LEDs you turn on, your LED strip could be drawing as much as 1.5 A.</p><p>Multiple LED strips can be chained together by connecting input connectors to output connectors. When strips are chained this way, they can be controlled and powered as one continuous strip. Please note, however, that as chains get longer, the ends will get dimmer and redder due to the voltage drop across the strip</p><p>We recommend chains of LEDs powered from a single supply not exceed 180 total RGB LEDs. It is fine to make longer chains with connected data lines, but you should power each 180-LED section separately. If you are powering each section from a different power supply, you should cut the power wires between the sections so you do not short the output of two different power supplies together.</p></blockquote><p>I’m using 60-LED per-meter strips, and due to my own calculations decided to split the strips every 2 meters. Now I’d rather do 3 meters (as recommended above) or 4 meters (since I almost never display at full brightness anyways).</p><p>To split the strips, I cut the strips with a wire cutter and soldered on <a href="http://www.jst-mfg.com/product/pdf/eng/eSM.pdf">4-pin JST connectors</a> at each end, using <a href="https://sugru.com/">Sugru</a> to re-create a protective cover over the silicon sheath on the LEDs to the wires.</p><p>Then I used the <a href="http://www.seeedstudio.com/depot/AllPixel-Power-Tap-Kit-p-2380.html">SEEED AllPixel Power Tap Kit</a> to connect in between the strips and re-inject the power using a large-ish 5V power supply and some custom soldered wires.</p><p>But this setup would break anytime I transported the LEDs to and from <a href="https://arthack.nz">Art~Hack</a> or a gig. Specifically the wires connecting to the Power Tap between the LED strips.</p><p>So I received the support from my friend Gordon of <a href="https://www.facebook.com/Fre3formD/">Fre3formD</a> to design and 3d print a custom enclosure for the Power Tap so that the pressure on the wires wouldn’t cause them to break.</p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_power-1.jpg 480w, /pixels-for-the-pixel-god/md_power-1.jpg 768w, /pixels-for-the-pixel-god/lg_power-1.jpg 992w, /pixels-for-the-pixel-god/xl_power-1.jpg 1280w, /pixels-for-the-pixel-god/2xl_power-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_power-1.jpg"  src="/pixels-for-the-pixel-god/power-1.jpg" alt="" loading="lazy" width="1536" height="1152" /></div><br><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_power-2.jpg 480w, /pixels-for-the-pixel-god/md_power-2.jpg 768w, /pixels-for-the-pixel-god/lg_power-2.jpg 992w, /pixels-for-the-pixel-god/xl_power-2.jpg 1280w, /pixels-for-the-pixel-god/2xl_power-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_power-2.jpg"  src="/pixels-for-the-pixel-god/power-2.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>Since then I’ve also started using small 5V power supplies per injection, since it’s much easier to run normal AC power extensions and it’s much more flexible to accommodate any venue. But I also haven’t run more than 12 meters of LED strips at a time yet, nor any permanent installations using this recent setup.</p><h2 id="A-new-play-with-portable-rainbows">A new play with portable rainbows<a class="header-anchor" href="#A-new-play-with-portable-rainbows">§</a></h2><p>Have a working prototype using the esp32, some rotary encoders, a button, and an apa102 led strip:</p><p><a href="https://github.com/ahdinosaur/aburndance">https://github.com/ahdinosaur/aburndance</a></p><div class="video-embed" data-ratio="16:9" data-type="vimeo" data-src="https://player.vimeo.com/video/796509004?h=3253ba78be&background=1" data-title="(2017) PIXELS FOR THE PIXEL GOD"></div><p>You click the button to cycle through “modes” (currently have rainbow mode, star field mode, and convergence mode). Each mode can use params from the first 3 rotary encoders, 4th rotary encoder is always used for global brightness.</p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_rainbow-breadboard-1.jpg 480w, /pixels-for-the-pixel-god/md_rainbow-breadboard-1.jpg 768w, /pixels-for-the-pixel-god/lg_rainbow-breadboard-1.jpg 992w, /pixels-for-the-pixel-god/xl_rainbow-breadboard-1.jpg 1280w, /pixels-for-the-pixel-god/2xl_rainbow-breadboard-1.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_rainbow-breadboard-1.jpg"  src="/pixels-for-the-pixel-god/rainbow-breadboard-1.jpg" alt="" loading="lazy" width="1440" height="1920" /></div></p><p>Today ported my setup from a breadboard to a protoboard, plus found some fancy knobs lying around the space!</p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_rainbow-breadboard-2.jpg 480w, /pixels-for-the-pixel-god/md_rainbow-breadboard-2.jpg 768w, /pixels-for-the-pixel-god/lg_rainbow-breadboard-2.jpg 992w, /pixels-for-the-pixel-god/xl_rainbow-breadboard-2.jpg 1280w, /pixels-for-the-pixel-god/2xl_rainbow-breadboard-2.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_rainbow-breadboard-2.jpg"  src="/pixels-for-the-pixel-god/rainbow-breadboard-2.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p><p>🎈</p><h3 id="Off-grid-new-years’">Off-grid new years’<a class="header-anchor" href="#Off-grid-new-years’">§</a></h3><p>Portable rainbows and me for off-grid new years’ 🎊</p><p><div class="image-wrapper"><img srcset="/pixels-for-the-pixel-god/sm_portable-rainbows.jpg 480w, /pixels-for-the-pixel-god/md_portable-rainbows.jpg 768w, /pixels-for-the-pixel-god/lg_portable-rainbows.jpg 992w, /pixels-for-the-pixel-god/xl_portable-rainbows.jpg 1280w, /pixels-for-the-pixel-god/2xl_portable-rainbows.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/pixels-for-the-pixel-god/lg_portable-rainbows.jpg"  src="/pixels-for-the-pixel-god/portable-rainbows.jpg" alt="" loading="lazy" width="1536" height="1152" /></div></p>]]></content>
    
    
    <summary type="html">As a break from my other open source projects, have decided to start spending my Art~Hack evenings on my next (3rd?) generation of modular LED pixels that beat to music.</summary>
    
    
    <content src="http://blog.mikey.nz/pixels-for-the-pixel-god/portable-rainbows.jpg" type="image"/>
    
    
    <category term="projects" scheme="http://blog.mikey.nz/categories/projects/"/>
    
    
    <category term="project" scheme="http://blog.mikey.nz/tags/project/"/>
    
    <category term="led" scheme="http://blog.mikey.nz/tags/led/"/>
    
  </entry>
  
  <entry>
    <title>Why I don&#39;t watch television</title>
    <link href="http://blog.mikey.nz/why-i-dont-watch-television/"/>
    <id>http://blog.mikey.nz/why-i-dont-watch-television/</id>
    <published>2017-08-18T12:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.277Z</updated>
    
    <content type="html"><![CDATA[<p>A television exercises your ability to watch without action for extended periods of time.</p><p>I want to exercise my ability to find creative solutions to complex problems.</p><span id="more"></span><p><div class="image-wrapper"><img srcset="/why-i-dont-watch-television/sm_leunig-tv-splash.jpg 480w, /why-i-dont-watch-television/md_leunig-tv-splash.jpg 768w, /why-i-dont-watch-television/lg_leunig-tv-splash.jpg 992w, /why-i-dont-watch-television/xl_leunig-tv-splash.jpg 1280w, /why-i-dont-watch-television/2xl_leunig-tv-splash.jpg 1536w" sizes="(min-width: 992px) 992px, 100vw" src="/why-i-dont-watch-television/lg_leunig-tv-splash.jpg"  src="/why-i-dont-watch-television/leunig-tv-splash.jpg" alt="" loading="lazy" width="1200" height="930" /></div></p><h2 id="The-medium-is-the-message"><a href="https://en.wikipedia.org/wiki/The_medium_is_the_message">The medium is the message</a><a class="header-anchor" href="#The-medium-is-the-message">§</a></h2><p>Humans are composed of instruments that cooperate to sense and action.</p><p>The instruments for how we sense and action is embedded in how the message is perceived.</p><p>For example: to watch a documentary is about how to sit on the couch and receive information.</p><h2 id="Creative-muscle">Creative muscle<a class="header-anchor" href="#Creative-muscle">§</a></h2><p>Our ability to be creative is a muscle that we must exercise to be fit.</p><p>If you only consume creative content, your muscles with atrophy from lack of exercise.</p><h2 id="Television">Television<a class="header-anchor" href="#Television">§</a></h2><p>Television is a way to receive sensory perception from a remote source. Television leaves little room for sensory imagination.</p><p>After not watchcing television for some years, now I find it too stimulating, overwhelming without control.</p><p>Watching a television is like entering a car and going for a ride.</p><h2 id="Relax">Relax<a class="header-anchor" href="#Relax">§</a></h2><p>A common reason for watching television is to relax.</p><p>Here are some other ways to relax:</p><h3 id="Listen-to-your-inner-mind">Listen to your inner mind<a class="header-anchor" href="#Listen-to-your-inner-mind">§</a></h3><p>Be quiet, hang out in your own self</p><h3 id="Write-in-a-journal">Write in a journal<a class="header-anchor" href="#Write-in-a-journal">§</a></h3><p>Create a trail of your thoughts</p><h3 id="Read-or-listen-to-a-book">Read or listen to a book<a class="header-anchor" href="#Read-or-listen-to-a-book">§</a></h3><p>An author of a book makes less decisions about sensory perception than television.</p><p>Sense the author’s cues, then create the story in your sensory imagination.</p><h3 id="Connect-with-your-friends">Connect with your friends<a class="header-anchor" href="#Connect-with-your-friends">§</a></h3><p>Share your trails</p><p>Talk about what’s on your mind</p><h3 id="Be-an-explorer-in-new-worlds">Be an explorer in new worlds<a class="header-anchor" href="#Be-an-explorer-in-new-worlds">§</a></h3><p>Follow other trails, but don’t follow a set path</p><h2 id="Why-does-this-matter">Why does this matter?<a class="header-anchor" href="#Why-does-this-matter">§</a></h2><p>You are what you eat.</p><p>The creative content you eat becomes you, the decisions made by the author.</p><p>Exercising your creative muscle helps you make your own creative decisions.</p><p>Interesting people are those who explore and follow new paths.</p><hr><p>Note: something I found after writing this: <a href="https://www.ratical.org/ratville/AoS/4Args4ElimTV.html">Four Arguments For The <em>Elimination</em> of Television</a>, via <code>%zT/UTPgTkTZhHTPZferhrXt2lU6+8gPygnnsro1FLpY=.sha256</code></p>]]></content>
    
    
    <summary type="html">A television exercises your ability to watch without action for extended periods of time.

I want to exercise my ability to find creative solutions to complex problems.</summary>
    
    
    <content src="http://blog.mikey.nz/why-i-dont-watch-television/leunig-tv-splash.jpg" type="image"/>
    
    
    
  </entry>
  
  <entry>
    <title>Luddite.js</title>
    <link href="http://blog.mikey.nz/luddite-js/"/>
    <id>http://blog.mikey.nz/luddite-js/</id>
    <published>2017-08-06T00:00:00.000Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>Here’s a talk about</p><p><strong>decentralized userland ecosystems</strong></p><p>and</p><p><strong>simple patterns based on function signatures</strong>.</p><span id="more"></span><div  class="video-embed"  data-ratio="16:9"  data-type="vimeo"  data-src="https://player.vimeo.com/video/239373498"  data-title="luddite.js - Mikey Williams"></div>]]></content>
    
    
    <summary type="html">Here’s a talk about

decentralized userland ecosystems

and

simple patterns based on function signatures.</summary>
    
    
    
    
    <category term="talk" scheme="http://blog.mikey.nz/tags/talk/"/>
    
  </entry>
  
  <entry>
    <title>Nature&#39;s Best Practices for Distributed Systems</title>
    <link href="http://blog.mikey.nz/natures-best-practices-for-distributed-systems/"/>
    <id>http://blog.mikey.nz/natures-best-practices-for-distributed-systems/</id>
    <published>2016-07-16T21:17:53.710Z</published>
    <updated>2025-05-27T10:46:54.273Z</updated>
    
    <content type="html"><![CDATA[<p>Here’s a talk about how</p><p><strong>fractal small subjective</strong> worlds</p><p>are better than</p><p><strong>flat global objective</strong> worlds.</p><span id="more"></span><iframe  width="560"  height="315"  src="https://www.youtube-nocookie.com/embed/EBM8RTlu6lU?si=nRm084DEqsleTAgf"  title="YouTube video player"  frameborder="0"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"  allowfullscreen></iframe>]]></content>
    
    
    <summary type="html">Here’s a talk about how

fractal small subjective worlds

are better than

flat global objective worlds.</summary>
    
    
    
    
    <category term="talk" scheme="http://blog.mikey.nz/tags/talk/"/>
    
  </entry>
  
  <entry>
    <title>Global vs local trust networks</title>
    <link href="http://blog.mikey.nz/global-vs-local-systems/"/>
    <id>http://blog.mikey.nz/global-vs-local-systems/</id>
    <published>2015-04-19T13:59:36.601Z</published>
    <updated>2015-04-19T16:31:04.267Z</updated>
    
    <content type="html"><![CDATA[<p>Many moons ago, I made a comment in the <a href="https://www.loomio.org/g/exAKrBUp/open-app-ecosystem">Open App Ecosystem Loomio</a> about <a href="https://www.loomio.org/d/fZs7J9v3/managed-trust#comment-174865">the difference between global and local trust networks and why I’m most interested in local trust networks</a>. This conversation keeps coming up, so I want to refine what I mean and elaborate further.</p><h2 id="Trust-networks">Trust networks<a class="header-anchor" href="#Trust-networks">§</a></h2><p>Systems that organize agents (people, groups, or bots) into interacting together create trust networks.</p><h3 id="Global">Global<a class="header-anchor" href="#Global">§</a></h3><p>In global trust networks like Bitcoin, Ethereum, or MaidSafe, the intent is to only have to trust cryptography and the algorithms that run the network. This lack of inter-agent trust comes at a cost, which is usually duplication of work. For Bitcoin this cost is large amounts of duplicate CPU work in Proof-of-Work cycles, or in MaidSafe this cost is large amounts of duplicate data.</p><h3 id="Local">Local<a class="header-anchor" href="#Local">§</a></h3><p>In local trust networks, we hope to centralize the system on our identities, both us as a person and us as a member of groups. Between these identities, we hope to create trust relationships. These trust relationships form the topology of the network, so if we are sharing resources (CPU, data, food, shelter, …), we can share directly instead of through a global network. This does mean though that we have to trust cryptography, the algorithms, and the identities we have trust relationships with.</p><p>TODO</p><h2 id="Further-reading">Further reading:<a class="header-anchor" href="#Further-reading">§</a></h2><ul><li><a href="http://networkcultures.org/unlikeus/resources/articles/what-is-a-federated-network/">“Beyond distributed and decentralized: what is a federated network?”</a></li><li><a href="http://www.ribbonfarm.com/2015/03/04/gardens-need-walls-on-boundaries-ritual-and-beauty/">“Gardens Need Walls: On Boundaries, Ritual, and Beauty”</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Many moons ago, I made a comment in the &lt;a href=&quot;https://www.loomio.org/g/exAKrBUp/open-app-ecosystem&quot;&gt;Open App Ecosystem Loomio&lt;/a&gt; abou</summary>
      
    
    
    
    
  </entry>
  
</feed>
