<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>TheStaticTurtle</title><description> Let's hack</description><link>https://blog.thestaticturtle.fr/</link><image><url>https://blog.thestaticturtle.fr/img/logo_white_hu69b57bfacdbf8ac055cd409b1b11e1ce_35351_512x512_resize_q100_bgffffff_box_3.jpg</url><title>TheStaticTurtle</title><link>https://blog.thestaticturtle.fr/</link></image><generator>Hugo</generator><lastBuildDate>Fri, 14 Nov 2025 23:00:00 +0000</lastBuildDate><atom:link href="https://blog.thestaticturtle.fr/index.xml" rel="self" type="application/rss+xml"/><ttl>60</ttl><language>en-GB</language><copyright>Copyright 2023 - TheStaticTurtle</copyright><item><title> Ultranet adventures part 2: Stagebox!</title><description> &lt;p>Continuing the Ultranet adventure by building a fully functional 8-channel stagebox.&lt;/p></description><link>https://blog.thestaticturtle.fr/ultranet-adventures-part-2/</link><guid>https://blog.thestaticturtle.fr/ultranet-adventures-part-2/</guid><category> Electronics</category><category> Open source</category><category> Diy</category><category> Audio</category><category> Concerts</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 14 Nov 2025 23:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/ultranet-adventures-part-2/images/cover2_hufcc652ca4acf6b21e48d678ca489c27c_1299378_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/ultranet-adventures-part-2/images/cover2_hufcc652ca4acf6b21e48d678ca489c27c_1299378_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Ultranet adventures part 2: Stagebox!</h1>
<span class="subtitle"><p>Continuing the Ultranet adventure by building a fully functional 8-channel stagebox.</p></span>
<br>

    <img class="" src='/ultranet-adventures-part-2/images/cover2_hufcc652ca4acf6b21e48d678ca489c27c_1299378_1350x900_fit_q80_box.jpg' alt="Ultranet adventures part 2: Stagebox!"/>

<hr>

<blockquote class="warning">
    
    
        <p class="warning-text">
This is a continuation of an already stupidly long adventure; you can read <a href="/ultranet-adventures-part-1/">part 1 here</a> but you'll probably need some time to really read it. If you are here just for the demo of the end product, you can click <a href="#demo">here</a>!
</p>
    
</blockquote>
<p>Welcome back!</p>
<p>In the last article, I heavily focused on understanding/reverse-engineering Ultranet and getting a proof of concept working on FPGA. It was mostly about the protocol side: understanding the physical side, how data flows, timings, and wrestling with bits until both transmitter and receiver behaved correctly. As we'll see later, it turns out that I was wrong about the implementation!</p>
<p>In this article, I'm shifting gears to design a product that I'd actually use in live production.</p>
<p>The whole point of this project is to create an 8-channel stagebox for non-critical auxiliary audio lines that I will use in my live production. I recently received the dates and song list for the 2026 &quot;tour&quot; so timelines on multiple projects, including this one, got very real 🤩!</p>
<p>As discussed in <a target="_blank" href="/ultranet-adventures-part-1/">part 1</a>, there are many options on the market (<a target="_blank" href="https://en.wikipedia.org/wiki/ADAT_Lightpipe">ADAT</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/MADI">MADI</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Dante_%28networking%29">Dante</a>, …), but those are (mostly) locked down and expensive. Reusing an existing protocol (like Behringer's Ultranet) is an easy way to design a futureproof(-ish) system while ensuring compatibility with many existing devices, all while learning about the intricacies of the system. This makes me more aware of the limits of a setup and lets me understand why things go wrong and how to bodge said things when it breaks 1h before go-time 😢.</p>
<blockquote class="warning">
    
    
        <p class="warning-text">
Due to various reasons, I will stop referring to my project as Ultranet.
As it is based on AES3, which is an open standard, I doubt I will annoy Behringer too much by publishing this project under a different name. 😅
Therefore, please welcome to the stage: <b>HyperNet</b> 🥁
</p>
    
</blockquote>
<h2 id="where-i-left-off">
    Where I left off 
    
    <a class="header-link" href="#where-i-left-off">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>At the end of <a target="_blank" href="/ultranet-adventures-part-1/">part 1</a>, everything was technically working, but not exactly production-ready:</p>
<ul>
<li>🧮 Channel indexing is still an issue; for some reason, real Ultranet receivers are still not fully compatible with HyperNet, and the HyperNet receiver had indexing issues.</li>
<li>🔌 The audio frontends of the DACs and ADCs were mediocre at best.</li>
<li>🧩 The PCB layout design was made to be a prototype devboard rather than a real product</li>
<li>📦 The project was a bare PCB without any case</li>
</ul>
<h2 id="quick-recap--correction">
    Quick Recap / Correction 
    
    <a class="header-link" href="#quick-recap--correction">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="aes3">
    AES3 
    
    <a class="header-link" href="#aes3">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As a reminder, AES3 is designed primarily to support stereo <a target="_blank" href="https://en.wikipedia.org/wiki/PCM">PCM</a> 📊 audio encoded using <a target="_blank" href="https://en.wikipedia.org/wiki/Biphase_mark_code">biphase mark code (BMC)</a>.</p>
<blockquote>
<p>Biphase mark code, also known as differential Manchester encoding, is a method to transmit data in which the data 💾 and clock 🕓 signals are combined to form a single two-level self-synchronizing data stream.</p>
</blockquote>
<p>AES3 is composed of what are called audio blocks. These audio blocks are composed of 192 frames, each frame contains 2 subframes, which, in turn, contain 32 time slots.</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/aes3-block-structure.drawio.png" >
            <img alt="AES3 Block structure" src="/ultranet-adventures-part-2/diagrams/aes3-block-structure.drawio.png" />
        </a>
        <figcaption>AES3 Block structure</figcaption>
    </figure>

</p>
<p>A subframe is composed of:</p>
<table>
<thead>
<tr>
<th>Time slot</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0–3</td>
<td>Preamble</td>
<td>A synchronization preamble</td>
</tr>
<tr>
<td>4–7</td>
<td>Auxiliary sample</td>
<td>A low-quality auxiliary channel used as specified in the channel status word.</td>
</tr>
<tr>
<td>8–27</td>
<td>Audio sample</td>
<td>Audio sample stored MSB last. It can be extended to use the auxiliary sample to increase quality</td>
</tr>
<tr>
<td>28</td>
<td>Validity (V)</td>
<td>Unset if the audio data is correct and suitable for D/A conversion.</td>
</tr>
<tr>
<td>29</td>
<td>User data (U)</td>
<td>Forms a serial data stream for each channel.</td>
</tr>
<tr>
<td>30</td>
<td>Channel status (C)</td>
<td>Bits from each subframe of an audio block are collated, giving a 192-bit channel status word.</td>
</tr>
<tr>
<td>31</td>
<td>Parity (P)</td>
<td>Even parity bit for detection of errors in data transmission. Excludes preamble; bits 4–31 need an even number of ones.</td>
</tr>
</tbody>
</table>
<p>
    <figure>
        <a target="_blank" href="diagrams/aes3-subframe.drawio.png" >
            <img alt="AES3 Subframe structure" src="/ultranet-adventures-part-2/diagrams/aes3-subframe.drawio.png" />
        </a>
        <figcaption>AES3 Subframe structure</figcaption>
    </figure>

</p>
<p>The preamble can be one of three values:</p>
<table>
<thead>
<tr>
<th style="text-align:center">Name</th>
<th style="text-align:center">Timeslot (Last was 0)</th>
<th style="text-align:center">Timeslot (Last was 1)</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">Z or B</td>
<td style="text-align:center">11101000</td>
<td style="text-align:center">00010111</td>
<td>Marks a word for channel A (left), and the <strong>start of an audio block</strong></td>
</tr>
<tr>
<td style="text-align:center">X or M</td>
<td style="text-align:center">11100010</td>
<td style="text-align:center">00011101</td>
<td>Marks a word for channel A (left)</td>
</tr>
<tr>
<td style="text-align:center">Y or W</td>
<td style="text-align:center">11100100</td>
<td style="text-align:center">00011011</td>
<td>Marks a word for channel B (right)</td>
</tr>
</tbody>
</table>
<p>Between the AES3 and S/PDIF standards, the contents of the 192-bit channel status word differ significantly, although they mutually agree that the first channel status bit distinguishes between the two 🤝. In the case of AES3, the standard describes, in detail, the function of each bit.</p>
<p>Broadly speaking, the channel status word indicates the type of data and has information about the clock and various metadata such as channel origin/destination.</p>
<h3 id="ultranet">
    Ultranet 
    
    <a class="header-link" href="#ultranet">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The initial assumption that Ultranet squishes 🗜️ eight 48 kHz audio channels into a single 192 kHz AES3 stream turned out to be correct. In other words, the eight channels are interleaved into one high-rate AES3-like stream so that each 48 kHz channel fits into the 192 kHz timing without resampling.</p>
<p>However, initially, I assumed that Ultranet relied on the AES3 B-frame for channel synchronization, which seemed like a good idea since the protocol is based on AES3 🤔. That assumption was partly correct; it allowed me to receive audio, but it failed when it came to consistent channel indexing while receiving. At the same time, the transmitter either produced misaligned channels or didn't work whatsoever. This meant that the actual synchronization mechanism had to be different 🤨.</p>
<h3 id="the-p16-m-tangent">
    The P16-M tangent 
    
    <a class="header-link" href="#the-p16-m-tangent">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>This is when Christian Nöding contacted me again. He was working on the Ultranet output of the X32 for the <a target="_blank" href="https://github.com/OpenMixerProject/OpenX32">OpenX32</a> project. I promptly sent him my code to try!</p>
<p>He tested it on a P16-M, and all he got was garbage 🗑️, random noise instead of usable audio. That immediately caught my attention. If his implementation didn't work on an actual Behringer device, something was clearly wrong in our understanding of the protocol.</p>
<p>By pure luck 🍀, I stumbled across a used P16-M listed for almost nothing on my local marketplace, so I grabbed it as soon as I could. Once it arrived, I hooked it up to my setup (the same one that worked flawlessly with the TFX122M-AN) and got the same garbage output. That was both good and bad news. On one hand it meant my implementation wasn't truly compatible, but on the other hand, now, I could reproduce the problem on real hardware and dig deeper into what was actually going on.</p>
<p>I then spent quite a while trying to debug things. At some point I got close and got channels 1-2 working, but after 2 days on the problem I had enough and decided that I wasn't going to test things blindly anymore 👀.</p>
<p>After a break, I promptly started to disassemble the mixer. At first glance/probe, I was surprised that none of the AK4114 appeared to have their B-frame pins connected to the XMOS chip 🤔. This is what set off alarm bells. I promptly soldered jumper wires on the SPI bus used to configure the chips and was again surprised by how little communication there was:</p>
<p>
    <figure>
        <a target="_blank" href="images/DSView_2025-10-02_18-44-02_5d9813bb-6ec0-4618-b2fe-cd0f5a06eb3a.png" >
            <img alt="P16-M AK4114 Config" src="/ultranet-adventures-part-2/images/DSView_2025-10-02_18-44-02_5d9813bb-6ec0-4618-b2fe-cd0f5a06eb3a.png" />
        </a>
        <figcaption>P16-M AK4114 Config</figcaption>
    </figure>

</p>
<p>After a quick read of the datasheet, it turns out that their config is very standard. The config sent to the chips is setting one of them to use the crystal as a clock source and the other one as a slave. The config also re-routes their input directly to the output pin for the passthrough port.</p>
<p>I then snooped on the I2S output and B-frame output, which looked like what you would expect. I was so determined to find something weird that I went as far as managing to rebuild a WAV file from a 20-second capture done with the logic analyzer, which also worked just fine 🔊.</p>
<p>Reluctantly, I decided that I might learn something by desoldering the chips 🔥. This confirmed that the only thing connected to the XMOS chip was the I2S signal and the &quot;valid&quot; output. This effectively confirmed that <strong>synchronization was in no way tied to the B-frame,</strong> and as the channel and user bits weren't connected either, it left only one place where the XMOS chip could sync: <strong>the sample data</strong></p>
<h3 id="back-to-research">
    Back to research 
    
    <a class="header-link" href="#back-to-research">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Now, a sync signal in the sample data sounds wrong. How the hell did they fit 24-bit audio + sync into 24 time-slots 🤨 ????</p>
<p>After some google-fu, it turns out that they don't. Ultranet doesn't use 24-bit PCM, instead it uses <strong>22-bit PCM</strong>.<br>
In hindsight, I should have noticed it earlier; most quick start guides from Behringer mention that their A/D conversion is 24-bit, but they do not mention anything about the actual data.</p>
<p>However the guide of some devices (like the <a target="_blank" href="https://www.la-bs.com/ObjetsMultimedia/42473/FR/DL32_MIDAS_me.pdf">DL32</a>) has an interesting line:</p>
<blockquote>
<p>ULTRANET networking @ 48 or 44.1 kHz, 22-bit PCM</p>
</blockquote>
<p>At the time I thought that was a typo 🤦, but I now realize that it is indeed correct. While the P16-M (and other devices that receive Ultranet) probably use 24 bits for their internal signal processing and the digital-to-analog conversion, the actual digital data transmitted over Ultranet is only 22-bit.</p>
<p>So, what is in those two bits? A bit more digging later, I found a few things on the web:</p>
<ul>
<li><a target="_blank" href="https://reverseengineering.stackexchange.com/a/11337">https://reverseengineering.stackexchange.com/a/11337</a></li>
<li><a target="_blank" href="https://github.com/tuck1s/UltranetReceiver/blob/master/app_ultranet/src/i2s-32bit-simple.xc#L137">https://github.com/tuck1s/UltranetReceiver/blob/master/app_ultranet/src/i2s-32bit-simple.xc#L137</a></li>
<li><a target="_blank" href="https://github.com/doughadfield/Ultranet-to-I2S/blob/main/ultranet.c#L248">https://github.com/doughadfield/Ultranet-to-I2S/blob/main/ultranet.c#L248</a></li>
</ul>
<p>All of these projects implement some sort of sync based on the sample data 🤯.</p>
<h3 id="ultranet-sync--channel-index">
    Ultranet sync &amp; channel index 
    
    <a class="header-link" href="#ultranet-sync--channel-index">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Ok, enough teasing. As I said before, since the data itself was correct and I was just receiving channels with an offset, it meant that the block structure was already correct since:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/ultranet-block-structure.drawio.png" >
            <img alt="Ultranet block structure" src="/ultranet-adventures-part-2/diagrams/ultranet-block-structure.drawio.png" />
        </a>
        <figcaption>Ultranet block structure</figcaption>
    </figure>

</p>
<blockquote class="warning">
    
    
        <p class="warning-text">
Channel 1 (read subframe 1) being the first in each frame is an editorial choice for this article; the position of the subframe in the frame is not relevant for Ultranet
</p>
    
</blockquote>
<p>The subframe, however, is a bit different:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/ultranet-subframe.drawio.png" >
            <img alt="Ultranet subframe structure" src="/ultranet-adventures-part-2/diagrams/ultranet-subframe.drawio.png" />
        </a>
        <figcaption>Ultranet subframe structure</figcaption>
    </figure>

</p>
<table>
<thead>
<tr>
<th>Time slot</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0–3</td>
<td>Preamble</td>
<td>Synchronization preamble, same as AES3</td>
</tr>
<tr>
<td>4–5</td>
<td>Channel index</td>
<td>Index of a &quot;left/right&quot; channel pair</td>
</tr>
<tr>
<td>6–27</td>
<td>Audio sample</td>
<td>Audio sample stored MSB last.</td>
</tr>
<tr>
<td>28</td>
<td>Validity (V)</td>
<td>Set if the audio data is correct and suitable for D/A conversion.</td>
</tr>
<tr>
<td>29</td>
<td>User data (U)</td>
<td>Unused, set to 0.</td>
</tr>
<tr>
<td>30</td>
<td>Channel status (C)</td>
<td>Used, but structure is currently unknown</td>
</tr>
<tr>
<td>31</td>
<td>Parity (P)</td>
<td>Even parity bit for detection of errors in data transmission. Excludes preamble; bits 4–31 need an even number of ones.</td>
</tr>
</tbody>
</table>
<p>As you can see, it's stupidly simple, and after a quick analysis with the logic analyzer, here are the channel indices:</p>
<table>
<thead>
<tr>
<th>Channel</th>
<th>Index</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>00 - Left</td>
</tr>
<tr>
<td>2</td>
<td>00 - Right</td>
</tr>
<tr>
<td>3</td>
<td>01 - Left</td>
</tr>
<tr>
<td>4</td>
<td>01 - Right</td>
</tr>
<tr>
<td>5</td>
<td>10 - Left</td>
</tr>
<tr>
<td>6</td>
<td>10 - Right</td>
</tr>
<tr>
<td>7</td>
<td>11 - Left</td>
</tr>
<tr>
<td>8</td>
<td>11 - Right</td>
</tr>
</tbody>
</table>
<p>The choice of two bits is interesting; they could have used a third bit but instead chose to group them by two. That grouping explains why the channels offset always moved two at the same time 🙄.</p>
<p>I also find it very intriguing that the TFX122M-AN worked at all last time 🤔. It must use a different sync &quot;procedure&quot; than the P16-M 🤷‍♂️.</p>
<p>This is superb news 🥳 because while we lose some fidelity, the implementation just became WAY easier than using the B-frame signal:</p>
<ul>
<li>To receive, you just have to wait for two samples that have the same index and output them to the correct DAC.</li>
<li>To transmit, it's even simpler; just put the correct index with the correct channel .</li>
</ul>
<p>What's funny is that the channel status bits are still sometimes needed. I still have no idea what they mean, but for some reason it doesn't <em>always</em> work without them. Since they are not used anywhere, I guess that it's a simple pattern that is enough for the AK4114 to start decoding 🤷.</p>
<h2 id="whats-news">
    What's new?s 
    
    <a class="header-link" href="#whats-news">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that I've set the record straight, let's actually start with part 2. After wrestling with the implementation, it's now time to clean up the move beyond the code spaghetti. This round of changes is all about making the design more robust, modular, and rack-friendly. In short: less &quot;prototype held together with hopes and prayers&quot;, and more &quot;something I can trust&quot;.</p>
<p>When I did my last PCB order, I snuck in a devboard for the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/dix9211.pdf">DIX9211</a>, a <code>216-kHz Digital Audio Interface Transceiver</code>. This chip is similar to the AK4114 that's being used for almost every Ultranet product I've seen so far. It's partly used to replace the PLL1707 👋. It was responsible for generating the 24.576 MHz system clock that the FPGA used to decode and generate the AES3 data streams. <br>
I'll talk about it later, but this chip also replaces the AES3 receiver inside the FPGA, which simplifies a bunch of things.</p>
<p>Also new, are modular DAC and ADC boards with proper analog frontends. This ensures flexibility, upgradability and reparability within the system. Imagine having to rebuild the whole board because someone blew up an input 🤦</p>
<p>In addition, I'm introducing an RP2040 as a supervisor MCU. It will be used to set up everything to its proper state and interface between the FPGA and other components. At its core, this project is an 8-in 8-out signal processor with interconnects, so it could be used for much more than a digital snake!</p>
<p>A much-needed improvement is a proper 1U case and CAD models to fit everything properly (okay, okay, you got me, a shelf with 3D-printed faceplates, I promise it looks good and feels solid 😉, you'll see!).</p>
<p>The last thing I need to mention is the move from fully open-source implementations to using built-in IPs inside my FPGA. Specifically, the transmitter is now using the <a target="_blank" href="https://www.gowinsemi.com/en/support/ip_detail/194/">Gowin SPDIF TX</a> IP. While I do believe that a fully open implementation would be preferable. I also believe that using the best tools for the job is the smarter choice. I'm still very novice in the FPGA world, and while I could probably fix the previous implementation to make it do what I want, for me, it was just &quot;easier&quot; to use something that already works. Moreover, while this IP is proprietary, it is free, so… 🤷</p>
<h2 id="electronics-redesign">
    Electronics redesign 
    
    <a class="header-link" href="#electronics-redesign">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="dac">
    DAC 
    
    <a class="header-link" href="#dac">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>For the outputs, I decided to stick with the same DAC chip as in the prototype. It performed pretty well for my application and integrated nicely with the FPGA 👍. The big change is moving from single-ended outputs to a fully balanced design. That means cleaner audio, lower noise, and better compatibility with professional gear, especially when running long cables on stage or in a studio.</p>
<p>With the move to balanced audio, it also meant new connectors 🔌. While I've seen balanced audio on TRS jacks before (in fact, my sound card, the UA25EX, does it this way), the proper connector to use is the XLR connector, which is way bigger than the small 3.5 mm jack I was using before.</p>
<p>The PCM5100A part of  the schematic is super standard; it's basically a copy-paste 📋 of the datasheet. It's just configured for left-justified operation (via solder jumpers). Unfortunately, the PCM5100A is not a DAC with balanced outputs, so I need to adapt it somehow. The go-to, place &amp; forget IC for this is the DRV134 or the DRV135. These chips are wonderful, but they also cost 5 EUR apiece in low quantities; that would mean 80 EUR of line drivers for the whole system 🤑. Much too expensive!</p>
<p>Instead, I chose to do a bit more work and use the NE5532. Here is how it looks:</p>
<p>
    <figure>
        <a target="_blank" href="images/eeschema_2025-10-26_19-53-53_9d3cef85-44e5-4855-aa29-fcd53caa05bd.png" >
            <img alt="DAC balanced output frontend" src="/ultranet-adventures-part-2/images/eeschema_2025-10-26_19-53-53_9d3cef85-44e5-4855-aa29-fcd53caa05bd.png" />
        </a>
        <figcaption>DAC balanced output frontend</figcaption>
    </figure>

</p>
<p>So let's break it down. The NE5532 is composed of two independent operational amplifiers:</p>
<ul>
<li>The first one, U5B, is a simple non-inverting buffer ➡️. As I don't have a negative supply on this system, the audio signal is biased to be centered around 2.5V.</li>
<li>The second one, U5A, is again a simple buffer 🔀, but this time in an inverting configuration.</li>
</ul>
<p>It then goes to protection resistors and DC-blocking caps and finally ends up in the connector 🔌. The same frontend is repeated for the 2nd channel</p>
<p>The PCB itself is basic, although I took special care while routing the analog and digital parts to ensure that they didn't cross. The two ground planes are connected at one point under the PCM5100A as specified in the datasheet.</p>
<p>I also set a specific size limit to the PCB so that I could start 3D modeling 📐 as soon as possible:</p>
<p>
    <figure>
        <a target="_blank" href="images/pcbnew_2025-10-10_22-55-03_db6c8e0c-b597-4106-8443-9c52e056d361.png" >
            <img alt="DAC 3D Render" src="/ultranet-adventures-part-2/images/pcbnew_2025-10-10_22-55-03_db6c8e0c-b597-4106-8443-9c52e056d361.png" />
        </a>
        <figcaption>DAC 3D Render</figcaption>
    </figure>

</p>
<h3 id="adc">
    ADC 
    
    <a class="header-link" href="#adc">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The input side follows the same philosophy 🎓: keep the same ADC chip, but redesign the analog front-end for balanced operation. As said before, balanced inputs are essential to reject interference and ground noise. Since XLR inputs are used, the next &quot;logical&quot; step was adding phantom power. While the new board doesn't feature any configurable pre-amplifier (you could technically change the gain of the amp) to boost microphones and whatnot, I figured that adding phantom power couldn't hurt.</p>
<p>If I don't need it, I can simply disable it, but if I want to use microphones, I can simply use inline pre-amplifiers like the <a target="_blank" href="https://www.thomann.de/lu/klark_teknik_mic_booster_ct1.htm">Klark Teknik Mic Booster CT1</a>, which is a <code>compact dynamic microphone booster with high-quality preamps</code>. Super easy and pretty cheap!</p>
<p>It does add some complexity (especially since this modification came later, which meant the PCB had to stay the same size to avoid redesigning CAD models), extra power regulation, and proper protection circuits, but it opens up much more flexible use cases. Here is how one channel looks:</p>
<p>
    <figure>
        <a target="_blank" href="images/eeschema_2025-10-26_20-01-21_6758def2-6a7e-454d-827d-a50b63e7f925.png" >
            <img alt="ADC balanced input frontend" src="/ultranet-adventures-part-2/images/eeschema_2025-10-26_20-01-21_6758def2-6a7e-454d-827d-a50b63e7f925.png" />
        </a>
        <figcaption>ADC balanced input frontend</figcaption>
    </figure>

</p>
<p>As you might have guessed, there's also a go-to IC for this application, the INA137. But same as before, this chip is far too expensive 💸 (less so but still). Instead I choose to use the OPA1677.</p>
<p>But first, we need to talk about what the hell phantom power is 👻. Phantom power is the standard way of powering devices through the same XLR cable that carries the audio signal. Instead of running a separate power line, 48V is applied equally to pins 2 and 3 of a balanced input relative to pin 1. Because the voltage is identical on both pins, it doesn't disturb the differential audio signal. Fortunately, there are plenty of places online to get the technical specifications (IEC 61938:2018 being the official document). Basically the max current is 10 mA, and you only need to connect two 6.81k resistors between each signal pin and the power source. On the schematic, this is the job of R22 and R24. If you would like to learn more about phantom, I can recommend this page: <a target="_blank" href="https://sound-au.com/articles/p-48.htm">https://sound-au.com/articles/p-48.htm</a></p>
<p>Note that the specification says 6.81k, but I instead used 6.8k. This is because, apparently, at the time, getting 6.8k resistors with a low tolerance was complicated 🤷. By choosing 6.81k, the specifications ensured the proper tolerance. However, these days it isn't really a problem anymore.</p>
<p>Just after this is the protection circuit; first the signal goes through DC-blocking caps, followed by protection resistors and protection diodes. This circuit ensures that the signal will never have a voltage so far from the absolute maximum that it's going to break something. You'll notice that they are marked as DNP 🤔. It turns out they clamped the signal too much, so I just removed them, and I'll be careful about what I plug in. If you have suggestions, please let me know!</p>
<p>The signal is then biased to 2.5V and fed to an OPA1677 configured as a simple buffer, after which the signal goes through some passives and then goes to the PCM1808!</p>
<p>As before, the PCB itself is basic, and I also took care while routing the analog and digital parts. As with the DAC, the two ground planes are connected at one point under the PCM1808, as hinted in the datasheet.</p>
<p>I also reused the size limit of the PCB that I defined before.</p>
<p>
    <figure>
        <a target="_blank" href="images/pcbnew_2025-10-10_22-59-28_4ed37ba5-16cf-4491-af4e-2777696eee1b.png" >
            <img alt="ADC 3D Render" src="/ultranet-adventures-part-2/images/pcbnew_2025-10-10_22-59-28_4ed37ba5-16cf-4491-af4e-2777696eee1b.png" />
        </a>
        <figcaption>ADC 3D Render</figcaption>
    </figure>

</p>
<h3 id="mainboard">
    Mainboard 
    
    <a class="header-link" href="#mainboard">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The mainboard changed a lot; in fact, it changed twice while I started the writing process for this article:</p>
<p>
    <figure>
        <a target="_blank" href="images/HyperNet-2-MainBoard.png" >
            <img alt="Main board render" src="/ultranet-adventures-part-2/images/HyperNet-2-MainBoard.png" />
        </a>
        <figcaption>Main board render</figcaption>
    </figure>

</p>
<p>As you might be able to see, the SFP and Ultranet ports on the board are basically unchanged.
They don't really need to change as they worked perfectly on the prototype. Only the Ultranet TX and RX ports are swapped to facilitate routing and that's it!</p>
<p>This also applies to the FPGA, which is still the same Tang 9k that I used last time. It works very well and is easy to use, so why change it 🙂!</p>
<p>The whole right side of the board is dedicated to power management (3.3v digital, 5v analog, and 48v phantom) ⚡. Each input has its own switch to turn on phantom power instead of a global one.</p>
<p>The most significant change is the IDC ports to connect the ADCs and DACs boards. I chose IDC connectors mainly because they are very resilient, cheap, and widely used. That said, if I were to redesign everything, I would probably use FFC connectors to reduce the size 📏. These ports provide:</p>
<ul>
<li>An I2C port 💬 (Unused and unpopulated for my boards)</li>
<li>An I2S port 🔉 (different pinout for DACs and ADCs to facilitate routing)</li>
<li>Two GPIOs (Only used in the DAC connector to carry the mute signal 🔇)</li>
<li>3.3V Digital power ⚡</li>
<li>5V Analog power ⚡</li>
</ul>
<p>Here is a high-level view of the schematic that shows how everything connects to each other:</p>
<p>
    <figure>
        <a target="_blank" href="images/HyperNet-2-MainBoard-Schematic.png" >
            <img alt="Main board high-level view" src="/ultranet-adventures-part-2/images/HyperNet-2-MainBoard-Schematic_hu74730883caa67c447c87332f68fd2d71_437087_0x720_resize_q90_h2_box_3.webp" />
        </a>
        <figcaption>Main board high-level view</figcaption>
    </figure>

</p>
<p>The last thing that needs a special mention is the DIX9211. This chip is basically an AES3/SPDIF receiver that spits out I2S. This simplifies so much of the FPGA logic since receiving I2S is <strong>way</strong> easier than receiving AES3!</p>
<p>I'm configuring it with a Raspberry Pi Pico and a few DIP-switches. I also stole the pass-through idea from the P16-M and used the chip as a &quot;biphase router&quot; of sorts. I can configure what input port is used as the input for the DIR, and I can also choose which input port is used for the biphase output. This offers a lot of flexibility; for example, I could use my project to receive from the Ultranet port and at the same time send audio over the fiber link!</p>
<p>This chip was really a no-brainer compared to the PLL1707 that I used before, since for a few cents more, it adds easier decoding, biphase routing, clocks, etc. Massive boost in flexibility for the system!</p>
<h2 id="hdl-redesign">
    HDL redesign 
    
    <a class="header-link" href="#hdl-redesign">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="receiver">
    Receiver 
    
    <a class="header-link" href="#receiver">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Here is a diagram showing how the receiver works from a high-level view:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/receiver.drawio.png" >
            <img alt="High-level view of the HyperNet receiver" src="/ultranet-adventures-part-2/diagrams/receiver.drawio_huf1829795e8ab9e0080d378db4675fb07_476187_0x720_resize_q90_h2_box_3.webp" />
        </a>
        <figcaption>High-level view of the HyperNet receiver</figcaption>
    </figure>

</p>
<p>Since the DIX9211 outputs I2S it's pretty simple. The demuxer is the interesting part 🧐. You can find parts of the code below. In short, it loads bits from <code>sdata</code> on the <code>bclk</code> rising edge and saves the sample when <code>lrclk</code> transitions, saving the sample. When two successive samples have the same LSB, output them to their respective ports.</p>
<p>There's a bit of clock management to get the proper speeds for the four I2S outputs and a &quot;small&quot; block to make sure the I2S clocks are somewhat locked to the receiver  (which makes sure the samples aren't loaded when written), but that's it.</p>
<p>After that, the signals go to an I2S transmitter block that has been modified to deal with four outputs at once.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#09f;font-style:italic">-- Detect LR transition (channel boundary)</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>is_lr_changed  <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">when</span> zlrclk <span style="color:#555">/=</span> lrclk <span style="color:#069;font-weight:bold">else</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#09f;font-style:italic">-- Extract active part of the audio signal and the current LSB of said signal.</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#09f;font-style:italic">-- The sample_data signal is 32bit but only the upper 24 bits matter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>sample_audio_data <span style="color:#555">&lt;=</span> sample_data(<span style="color:#f60">31</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">10</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;00&#34;</span>; <span style="color:#09f;font-style:italic">-- Remove the LSB as this is used for the channel index</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>sample_lsb_data <span style="color:#555">&lt;=</span> sample_data(<span style="color:#f60">9</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">8</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#09f;font-style:italic">-- Main deserialization process</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>deserialize_i2s <span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(bclk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>	<span style="color:#069;font-weight:bold">if</span> rising_edge(bclk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>        <span style="color:#069;font-weight:bold">if</span> reset_n <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>            <span style="color:#09f;font-style:italic">-- Reset all outputs and counters</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>            sample_data            <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>            prev_sample_audio_data <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>            prev_sample_lsb_data   <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>            ch1_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>            ch2_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>            ch3_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>            ch4_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>            ch5_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>            ch6_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>            ch7_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>            ch8_out <span style="color:#555">&lt;=</span> (<span style="color:#069;font-weight:bold">others</span> <span style="color:#555">=&gt;</span> <span style="color:#c30">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>            data_ready <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>            <span style="color:#09f;font-style:italic">-- Keep the edge detector running while in reset</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>            zlrclk <span style="color:#555">&lt;=</span> lrclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>            <span style="color:#09f;font-style:italic">-- Shift serial input data into buffer (MSB first). 32 bits are shifted but only the upper 24 are valid data</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>            sample_data <span style="color:#555">&lt;=</span> sample_data(sample_data<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_data<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>            <span style="color:#09f;font-style:italic">-- Save LRCLK for edge detection</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>            zlrclk <span style="color:#555">&lt;=</span> lrclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>                
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>            <span style="color:#09f;font-style:italic">-- On word clock transition:</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>            <span style="color:#069;font-weight:bold">if</span> is_lr_changed <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>                <span style="color:#09f;font-style:italic">-- Check if current LSB matches previous LSB</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>                <span style="color:#069;font-weight:bold">if</span> sample_lsb_data <span style="color:#555">=</span> prev_sample_lsb_data <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>                    <span style="color:#09f;font-style:italic">-- Two successive samples with same LSB whe are successfuly synced - output them</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>                    <span style="color:#069;font-weight:bold">case</span> sample_lsb_data <span style="color:#069;font-weight:bold">is</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>                        <span style="color:#069;font-weight:bold">when</span> <span style="color:#c30">&#34;00&#34;</span> <span style="color:#555">=&gt;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>                            ch1_out <span style="color:#555">&lt;=</span> prev_sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>                            ch2_out <span style="color:#555">&lt;=</span> sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>                        <span style="color:#069;font-weight:bold">when</span> <span style="color:#c30">&#34;01&#34;</span> <span style="color:#555">=&gt;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>                            ch3_out <span style="color:#555">&lt;=</span> prev_sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>                            ch4_out <span style="color:#555">&lt;=</span> sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>                        <span style="color:#069;font-weight:bold">when</span> <span style="color:#c30">&#34;10&#34;</span> <span style="color:#555">=&gt;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>                            ch5_out <span style="color:#555">&lt;=</span> prev_sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>                            ch6_out <span style="color:#555">&lt;=</span> sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>                        <span style="color:#069;font-weight:bold">when</span> <span style="color:#c30">&#34;11&#34;</span> <span style="color:#555">=&gt;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span>                            ch7_out <span style="color:#555">&lt;=</span> prev_sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>                            ch8_out <span style="color:#555">&lt;=</span> sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>                            <span style="color:#09f;font-style:italic">-- When channel 8 is written, flag that the data is ready for 1 bclk cycle </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>                            <span style="color:#09f;font-style:italic">-- The choice of lsb=11 is arbitrary, unless the input data is really fucked-up it shouldn&#39;t matter. There really should be a separate data ready signal for each pair</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>                            data_ready <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>                    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">case</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62</span><span>                <span style="color:#09f;font-style:italic">-- Store sample and LSB as previous for next comparison</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">63</span><span>                prev_sample_audio_data <span style="color:#555">&lt;=</span> sample_audio_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">64</span><span>                prev_sample_lsb_data   <span style="color:#555">&lt;=</span> sample_lsb_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">65</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">66</span><span>            <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">67</span><span>                <span style="color:#09f;font-style:italic">-- Still reading data</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">68</span><span>                <span style="color:#09f;font-style:italic">-- Clear the data ready flag</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">69</span><span>                data_ready <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">70</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">71</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">72</span><span>	<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">73</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h3 id="transmitter">
    Transmitter 
    
    <a class="header-link" href="#transmitter">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Here is a diagram showing how the transmitter works from a high-level view:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/transmitter.drawio.png" >
            <img alt="High-level view of the HyperNet transmitter" src="/ultranet-adventures-part-2/diagrams/transmitter.drawio_hu3b954d6d75bc8ba81684f244e80f4775_655297_0x720_resize_q90_h2_box_3.webp" />
        </a>
        <figcaption>High-level view of the HyperNet transmitter</figcaption>
    </figure>

</p>
<p>As you can see, it's WAY more complicated than the receiver. This is mainly because I'm using a pre-made FIFO and S/PDIF transmitter block, there's not much code to show besides the diagram, but the most &quot;interesting&quot; part is the mux that pushes a burst of eight audio samples to the FIFO. Basically it waits for the signal that says that the FIFO is almost empty (e.g., finished with the last batch) and pushes the samples.</p>
<p>As before, there's a bit of logic to deal with the clock signals and their synchronization to the transmitter, but that part is pretty easy.</p>
<p>There's also a block to deal with the channel status. I don't really know why it's needed, but I simply use what the DL16 is sending.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>push_data <span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#069;font-weight:bold">if</span> falling_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>		<span style="color:#069;font-weight:bold">if</span> reset_n <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>            <span style="color:#09f;font-style:italic">-- Reset all outputs and counters</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            burst_write_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            sample_write <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            sample <span style="color:#555">&lt;=</span> <span style="color:#c30">&#34;000000000000000000000000&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            <span style="color:#09f;font-style:italic">-- Data from the I2S reader is ready, burst write the data to the FIFO</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            <span style="color:#069;font-weight:bold">if</span> data_ready <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>                burst_write_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">8</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>            
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>            <span style="color:#09f;font-style:italic">-- Write samples on a bclk pos edge</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>            <span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>                <span style="color:#069;font-weight:bold">if</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">8</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>                    sample <span style="color:#555">&lt;=</span> ch1_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;00&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">7</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>                    sample <span style="color:#555">&lt;=</span> ch2_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;00&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">6</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>                    sample <span style="color:#555">&lt;=</span> ch3_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;01&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">5</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>                    sample <span style="color:#555">&lt;=</span> ch4_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;01&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">4</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>                    sample <span style="color:#555">&lt;=</span> ch5_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;10&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">3</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>                    sample <span style="color:#555">&lt;=</span> ch6_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;10&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">2</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>                    sample <span style="color:#555">&lt;=</span> ch7_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;11&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>                <span style="color:#069;font-weight:bold">elsif</span> burst_write_counter <span style="color:#555">=</span> <span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>                    sample <span style="color:#555">&lt;=</span> ch8_in(<span style="color:#f60">23</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">2</span>) <span style="color:#555">&amp;</span> <span style="color:#c30">&#34;11&#34;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>                <span style="color:#069;font-weight:bold">if</span> burst_write_counter <span style="color:#555">&gt;</span> <span style="color:#f60">0</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>                    sample_write <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>                    burst_write_counter <span style="color:#555">&lt;=</span> burst_write_counter <span style="color:#555">-</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>            <span style="color:#069;font-weight:bold">elsif</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>                sample_write <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>		<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>	<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h2 id="firmware">
    Firmware 
    
    <a class="header-link" href="#firmware">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Since I chose to go with the DIX9211, I need a way to control it 🎛️. This could have been done in the FPGA, however, I didn't feel like implementing I2C by hand 🥱, so I decided to use a Raspberry Pi Pico. It takes up a bunch of space on the PCB, but it's way faster to work with.</p>
<p>Right now it's only configuring the chip, but as I said earlier, I did connect an SPI bus with two extra pins to the FPGA, this could be used for many applications ranging from basic config to audio input/output over USB.</p>
<p>I also alluded to earlier that, like the P16-M, I use the DIX9211 as a router 🔀. This is configured by the supervisor from some DIP switches:</p>
<table>
<thead>
<tr>
<th>Config</th>
<th>Behavior</th>
</tr>
</thead>
<tbody>
<tr>
<td>xx00</td>
<td>The output HyperNet signal is coming from fiber input</td>
</tr>
<tr>
<td>xx01</td>
<td>The output HyperNet signal is coming from the Ultranet 1-8 input.</td>
</tr>
<tr>
<td>xx10</td>
<td>The output HyperNet signal is coming from the Ultranet 9-16 input.</td>
</tr>
<tr>
<td><strong>xx11</strong></td>
<td><strong>The output HyperNet signal is coming from the FPGA</strong></td>
</tr>
<tr>
<td>-------------</td>
<td>-------------------------------------------------------</td>
</tr>
<tr>
<td><strong>00xx</strong></td>
<td><strong>The DIR input is coming from fiber input</strong></td>
</tr>
<tr>
<td>01xx</td>
<td>The DIR input is coming from the Ultranet 1-8 input.</td>
</tr>
<tr>
<td>10xx</td>
<td>The DIR input is coming from the Ultranet 9-16 input.</td>
</tr>
<tr>
<td>11xx</td>
<td>The DIR input is coming from the FPGA (loopback)</td>
</tr>
</tbody>
</table>
<p>There are a few additional registers to configure. The full config can be seen in the setup script below.<br>
Again, I didn't feel like setting up a full C++ project with the pico-sdk (or even Arduino), so I just left CircuitPython and made it so that the script runs at boot. This has the downside of a small 500ms delay before it's configured after reset, but I can live with that 🤷!</p>
<p>It could be improved by periodically watching the inputs 👀 and reconfiguring accordingly. It would remove the need to push the reset button after changing the config, but then again, I was lazy for this part of the project.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">board</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">busio</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">digitalio</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">time</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">adafruit_bus_device.i2c_device</span> <span style="color:#069;font-weight:bold">import</span> I2CDevice
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>source_map <span style="color:#555">=</span> [<span style="color:#c30">&#34;Fiber&#34;</span>, <span style="color:#c30">&#34;HyperNet 1-8&#34;</span>, <span style="color:#c30">&#34;HyperNet 9-16&#34;</span>, <span style="color:#c30">&#34;FPGA&#34;</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>i2c <span style="color:#555">=</span> busio<span style="color:#555">.</span>I2C(sda<span style="color:#555">=</span>board<span style="color:#555">.</span>GP12, scl<span style="color:#555">=</span>board<span style="color:#555">.</span>GP13)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>device_dix <span style="color:#555">=</span> I2CDevice(i2c, <span style="color:#f60">64</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">read</span>(device, register, size<span style="color:#555">=</span><span style="color:#f60">1</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>    buf <span style="color:#555">=</span> <span style="color:#366">bytearray</span>(size)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>    <span style="color:#069;font-weight:bold">with</span> device_dix:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>        device_dix<span style="color:#555">.</span>write_then_readinto(<span style="color:#366">bytes</span>([register]), buf)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>    <span style="color:#069;font-weight:bold">return</span> buf
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">write</span>(device, register, data):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>    buf <span style="color:#555">=</span> <span style="color:#366">bytearray</span>(<span style="color:#f60">1</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>    buf[<span style="color:#f60">0</span>] <span style="color:#555">=</span> register
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>    buf<span style="color:#555">.</span>extend(data)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>    <span style="color:#069;font-weight:bold">with</span> device_dix:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>        device_dix<span style="color:#555">.</span>write(buf)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>config_outsrc_1 <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>DigitalInOut(board<span style="color:#555">.</span>GP0)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>config_outsrc_1<span style="color:#555">.</span>direction <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>Direction<span style="color:#555">.</span>INPUT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>config_outsrc_2 <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>DigitalInOut(board<span style="color:#555">.</span>GP1)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>config_outsrc_2<span style="color:#555">.</span>direction <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>Direction<span style="color:#555">.</span>INPUT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>config_inpsrc_1 <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>DigitalInOut(board<span style="color:#555">.</span>GP2)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>config_inpsrc_1<span style="color:#555">.</span>direction <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>Direction<span style="color:#555">.</span>INPUT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>config_inpsrc_2 <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>DigitalInOut(board<span style="color:#555">.</span>GP3)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>config_inpsrc_2<span style="color:#555">.</span>direction <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>Direction<span style="color:#555">.</span>INPUT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>config_outsrc <span style="color:#555">=</span> (<span style="color:#000;font-weight:bold">not</span> config_outsrc_1<span style="color:#555">.</span>value) <span style="color:#555">&lt;&lt;</span> <span style="color:#f60">0</span> <span style="color:#555">|</span> (<span style="color:#000;font-weight:bold">not</span> config_outsrc_2<span style="color:#555">.</span>value) <span style="color:#555">&lt;&lt;</span> <span style="color:#f60">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>config_inpsrc <span style="color:#555">=</span> (<span style="color:#000;font-weight:bold">not</span> config_inpsrc_1<span style="color:#555">.</span>value) <span style="color:#555">&lt;&lt;</span> <span style="color:#f60">0</span> <span style="color:#555">|</span> (<span style="color:#000;font-weight:bold">not</span> config_inpsrc_2<span style="color:#555">.</span>value) <span style="color:#555">&lt;&lt;</span> <span style="color:#f60">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span><span style="color:#366">print</span>(<span style="color:#c30">f</span><span style="color:#c30">&#34;DIR Input source: </span><span style="color:#a00">{</span>source_map[config_inpsrc]<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span><span style="color:#366">print</span>(<span style="color:#c30">f</span><span style="color:#c30">&#34;DIR Output source: </span><span style="color:#a00">{</span>source_map[config_outsrc]<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>write(device_dix, <span style="color:#f60">0x2F</span>, [<span style="color:#f60">0b00000101</span>])    <span style="color:#09f;font-style:italic"># DIR Output Data Format</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>                                         <span style="color:#09f;font-style:italic">#   24-bit MSB first, left-justified</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>write(device_dix, <span style="color:#f60">0x31</span>, [<span style="color:#f60">0b00000000</span>])    <span style="color:#09f;font-style:italic"># XTI Source, Clock (SCK/BCK/LRCK) Frequency Setting</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>                                         <span style="color:#09f;font-style:italic">#   SCK=24.576M BCK=12.288M LRCK=192k </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>write(device_dix, <span style="color:#f60">0x34</span>, [config_inpsrc]) <span style="color:#09f;font-style:italic"># DIR Input Biphase Source Select, Coax Amplifier Control</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>                                         <span style="color:#09f;font-style:italic">#   rxin0amp normal / rxin1amp normal / rxin2 source</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>write(device_dix, <span style="color:#f60">0x6F</span>, [<span style="color:#f60">0b00011100</span>])    <span style="color:#09f;font-style:italic"># MPIO_A, MPIO_B, MPIO_C Group Function Assign</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>                                         <span style="color:#09f;font-style:italic">#   MPIO_A = Biphase Input Extension (RXIN8 to RXIN11)</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span>                                         <span style="color:#09f;font-style:italic">#   MPIO_B = DIR Flags Output or GPIO (Selected by MPB3SEL, MPB2SEL, MPB1SEL, MPB0SEL)</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>                                         <span style="color:#09f;font-style:italic">#   MPIO_C = DIR BCUV OUT, BFRAME/VOUT/UOUT/COUT</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>write(device_dix, <span style="color:#f60">0x71</span>, [<span style="color:#f60">0b00000000</span>])    <span style="color:#09f;font-style:italic"># MPIO_B, MPIO_C Flags or GPIO Assign Setting</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>                                         <span style="color:#09f;font-style:italic">#   DIR Flags, set by MPB1FLG / MPC0FLG</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>write(device_dix, <span style="color:#f60">0x74</span>, [<span style="color:#f60">0b01010101</span>])    <span style="color:#09f;font-style:italic"># MPIO_B1, MPIO_B0 Output Flag Select</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span>                                         <span style="color:#09f;font-style:italic">#   LOCK LOCK</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61</span><span>write(device_dix, <span style="color:#f60">0x75</span>, [<span style="color:#f60">0b01010101</span>])    <span style="color:#09f;font-style:italic"># MPIO_B3, MPIO_B2 Output Flag Select</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62</span><span>                                         <span style="color:#09f;font-style:italic">#   LOCK LOCK</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">63</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">64</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">65</span><span>write(device_dix, <span style="color:#f60">0x78</span>, [<span style="color:#f60">0b11001110</span>])    <span style="color:#09f;font-style:italic"># MPO1, MPO0 Function Assign Setting</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">66</span><span>                                         <span style="color:#09f;font-style:italic">#   MPO1=XMCKO MPO0=RECOUT0 </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">67</span><span>                          
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">68</span><span>write(device_dix, <span style="color:#f60">0x24</span>, [<span style="color:#f60">0b00010100</span>])    <span style="color:#09f;font-style:italic"># Oscillation Circuit Control</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">69</span><span>                                         <span style="color:#09f;font-style:italic">#   OSCAUTO=0 (always operates) XMCKENX=1 (Output) XMCKDIV=01 (XTI/2 (12.288 MHz))</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">70</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">71</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">72</span><span>write(device_dix, <span style="color:#f60">0x35</span>, [config_outsrc]) <span style="color:#09f;font-style:italic"># RECOUT0 Output Biphase Source Select</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">73</span><span>                                         <span style="color:#09f;font-style:italic">#   RECOUT0=RXIN3 MPO0MUT=Output</span>
</span></span></code></pre></div><h2 id="3d-design">
    3D Design 
    
    <a class="header-link" href="#3d-design">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Designing the physical enclosure for this project turned out to be quite fun 😫.
I didn't want to spend a bunch of money on a custom rack case, so instead I opted for the Scrooge McDuck 🦆 solution and used a standard 1U rack shelf. I've used this method in the past for my SDI over fiber project, so I knew it could work.</p>
<p>It's far from the best solution, but once everything is assembled, it's strong and, most importantly, very affordable. Using a shelf also means I don't have to worry about a custom front-panel or things like that. Instead I can just 3D-print the front and rear plates myself 👍.</p>
<p>Since the DAC and ADC modules have the same size, connector placement, and mounting holes, it was straightforward to design a holder for each. However, since this will be rack-mounted, I can't have screws on the bottom to attach the 3D prints. To solve this, I have 3 aluminum bars running for the whole length. These bars are then screwed in from the side:</p>
<p>
    <figure>
        <a target="_blank" href="images/stagebox_aluminium_bars.jpg" >
            <img alt="Shelf mounting system" src="/ultranet-adventures-part-2/images/stagebox_aluminium_bars_hu94c34b16aa6d99b311b8369f59982f4f_717805_0x720_resize_q90_h2_box.webp" />
        </a>
        <figcaption>Shelf mounting system</figcaption>
    </figure>

</p>
<p>I initially thought it wouldn't be enough and bought some strong double-sided tape. To my surprise, it turned out that it is surprisingly strong once all the holders are present 💪! My design made it so that 8 holders don't fill the full width, there's about a centimeter left that I use for status LEDs (like power, active, sync, …).</p>
<h3 id="dac--adc-holder">
    DAC &amp; ADC Holder 
    
    <a class="header-link" href="#dac--adc-holder">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>For the DAC/ADC holder, I designed a compact holder in which I can slide the PCB in from the rear and lock it against the faceplate with four M3 screws into the XLR connectors. Additionally, there are three M3 screws on the PCB itself; this increases the overall rigidity of the holder and makes sure it won't break when plugging in cables.</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_223201201.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_223201201.PORTRAIT_hubfe4efcfb37b81d35b342ef4006dd70b_750909_0x720_resize_q90_h2_box.webp" alt="DAC with the holder (Front)">
                        </a>
                        
                            <figcaption>DAC with the holder (Front)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_223156227.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_223156227.PORTRAIT_hu356ce749f40dc427d57f98c96d781996_1166588_0x720_resize_q90_h2_box.webp" alt="DAC with the holder (Back)">
                        </a>
                        
                            <figcaption>DAC with the holder (Back)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>The only difference between the ADC and DAC holder is that the ADC one has an extra hole near each XLR connector for an LED, which will indicate the presence of phantom power 💡. Soldering the LEDs was quite annoying due to the short leads, so if anyone knows where I can get LEDs with longer leads, I would appreciate knowing about it!</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_223232217.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_223232217.PORTRAIT_huf13e9c1d9e00c8d49beb372e8b00f13f_1228469_0x720_resize_q90_h2_box.webp" alt="ADC with the holder (Front)">
                        </a>
                        
                            <figcaption>ADC with the holder (Front)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_223243798.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_223243798.PORTRAIT_hu95580406af63b1af99071bbb2a53f7d2_1120650_0x720_resize_q90_h2_box.webp" alt="ADC with the holder (Back)">
                        </a>
                        
                            <figcaption>ADC with the holder (Back)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>I also designed some dust covers that you will see later. They slide in the dovetail-like slot of the sled. While it's by no means waterproof (or even dustproof), it will stop things from touching the PCB directly, which is the most important part for me.</p>
<h3 id="main-board">
    Main board 
    
    <a class="header-link" href="#main-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The mainboard is going to get stuck on the shelf with double-sided tape, so I just needed a simple plate on the bottom to isolate the PCB from the metal shelf. And to make everything look good, I also made a front plate to cover most of the components:</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_220910286.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_220910286.PORTRAIT_hu699281c05d4efb889976760a346508a6_1512174_0x720_resize_q90_h2_box.webp" alt="Mainboard with bottom plate">
                        </a>
                        
                            <figcaption>Mainboard with bottom plate</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251025_223110398.PORTRAIT.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251025_223110398.PORTRAIT_hub598d5d8f37a543a3ec0a7fd722e9bdd_1856672_0x720_resize_q90_h2_box.webp" alt="Mainboard with bottom and top plate">
                        </a>
                        
                            <figcaption>Mainboard with bottom and top plate</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>I left the FPGA in the open because it's the only part on headers because I might use it for other projects in the future!</p>
<h3 id="assembly">
    Assembly 
    
    <a class="header-link" href="#assembly">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>And it's finally time for assembly 🤩!</p>
<p>I started by putting the four DACs and four ADCs on their respective holders and sliding them into the shelf:</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_181901509.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_181901509.RAW-01.COVER_hu0ebc8e74c510f8f0a6b9735b65a6db1e_779434_0x720_resize_q90_h2_box.webp" alt="Final assembly (selds only, front)">
                        </a>
                        
                            <figcaption>Final assembly (selds only, front)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_181912164.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_181912164.RAW-01.COVER_hu6763f4eb3858dc168cd7af96c6794ada_1216116_0x720_resize_q90_h2_box.webp" alt="Final assembly (selds only, back)">
                        </a>
                        
                            <figcaption>Final assembly (selds only, back)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>I then stuck the mainboard (and power supply) with some strong double-sided tape:</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_182723208.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_182723208.RAW-01.COVER_hu271fe7401562d1a3416329830e948b80_1127063_0x720_resize_q90_h2_box.webp" alt="Final assembly (all components, front)">
                        </a>
                        
                            <figcaption>Final assembly (all components, front)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_182734890.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_182734890.RAW-01.COVER_hu0c876013ef7911162ca24aea20c13e82_1191413_0x720_resize_q90_h2_box.webp" alt="Final assembly (all components, back)">
                        </a>
                        
                            <figcaption>Final assembly (all components, back)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>And finally I made &quot;a few&quot; custom cables to make sure everything looks neat:</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_182947390.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_182947390.RAW-01.COVER_hu4bc2b434a8c2770e3bdf2bd2ec1b90c4_1065564_0x720_resize_q90_h2_box.webp" alt="Final assembly (with cables, front)">
                        </a>
                        
                            <figcaption>Final assembly (with cables, front)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/PXL_20251027_183003153.RAW-01.COVER.jpg">
                            <img src="/ultranet-adventures-part-2/images/PXL_20251027_183003153.RAW-01.COVER_hu1f95a145ac59171542d1437745733f90_1018468_0x720_resize_q90_h2_box.webp" alt="Final assembly (with cables, back)">
                        </a>
                        
                            <figcaption>Final assembly (with cables, back)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="validation">
    Validation 
    
    <a class="header-link" href="#validation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Validation was quite fun because I discovered a few issues 😢:</p>
<blockquote>
<p><em>These issues have been corrected in the schematics above</em></p>
</blockquote>
<ul>
<li>Audio was leaking between the 2 channels of each DAC board.
<ul>
<li>This turned out to be caused by my 2.5V bias supply, I used a simple divider connected to both channels, I should have used either a 2.5V LDO or what I ended up doing, which was adding a 2nd voltage divider just for the 2nd channel.</li>
</ul>
</li>
<li>Gain issue with the DAC board:
<ul>
<li>Solved it by using a 0-ohm resistor for the feedback resistor of the opamp for positive output instead of the 10k I had before.</li>
</ul>
</li>
<li>Same gain issue with the ADC board:
<ul>
<li>Also solved it by using a 0-ohm resistor for the feedback resistor of the opamp instead of the 10k I had before.</li>
</ul>
</li>
<li>Fiber input not working on the mainboard:
<ul>
<li>Missing 100k pull down resistor on the negative signal of the differential pair coming from the SFP.</li>
</ul>
</li>
</ul>
<p>But after solving all of these, everything worked 🥳 !</p>
<p>Thanks to my dad, who's an actual sound engineer, and the Smaart software, I even managed to do some proper measurements 📏:</p>
<p>
    <figure>
        <a target="_blank" href="images/Smart4.JPG" >
            <img alt="Measurement done with Smaart V6" src="/ultranet-adventures-part-2/images/Smart4_hu30673cf51a8ae206fc12c3e0fd9aa89a_279619_0x720_resize_q90_h2_box.webp" />
        </a>
        <figcaption>Measurement done with Smaart V6</figcaption>
    </figure>

</p>
<p>The signal is flat overall except in the lower frequencies. I suspect this is due to my choice of caps for the DAC 🤔, but it's more than good enough for me (in fact, it's better than what I measured at the output of the P16-M).</p>
<p>The software also gave me an estimate for latency, which is <strong>0.56 ms</strong> (it's the equivalent of sitting ~20 cm from a speaker), which is again more than good enough for my application!</p>
<h2 id="demo">
    Demo 
    
    <a class="header-link" href="#demo">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Ok enough text, let's see the demo 📹:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/-GW9iot34w0"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<p><em>I didn't show every possible configuration but you can probably figure out how it would work. The last demo (with two boards) was filmed before the 2nd shelf arrived, hence why it doesn't look the same</em>😅!</p>
<h2 id="conclusion--whats-next">
    Conclusion / What's next 
    
    <a class="header-link" href="#conclusion--whats-next">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This project started with me wanting to carry timecode and a few other signals from stage to FOH and back. It then promptly spiraled out of control 🌪️ and fed my curiosity about the inner workings of Behringer's Ultranet and ended up as a robust, open hardware system I can actually trust on stage.</p>
<p>Just like my previous project, it taught me a lot. I learned about AES3 (and Ultranet) internals, clock domains ⌚, analog design, etc. This is huge because now when something goes wrong, I actually know what to look for, I can better estimate the limits of the systems I use, and I can figure out solutions to problems way faster.
Working on a project like this not only gives you an insane amount of knowledge on the systems you are studying, but it also gives you a huge amount of respect for how much engineering goes into moving &quot;just sixteen channels of audio&quot;.</p>
<p>Unlike the first part, I feel confident enough with my implementation to release this publicly on my GitHub. I do have to warn you that it's not a plug-and-play solution and requires a lot of work!</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/HyperNet" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/6e9a5ecd0357827e004d7621d1e27956_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/6e9a5ecd0357827e004d7621d1e27956_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='HyperNet is an 8-in, 8-out open-source audio stagebox based on Ultranet - TheStaticTurtle/HyperNet'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/HyperNet">GitHub - TheStaticTurtle/HyperNet: HyperNet is an 8-in, 8-out open-source audio stagebox based on Ultranet</a>
        
        
            <p>HyperNet is an 8-in, 8-out open-source audio stagebox based on Ultranet - TheStaticTurtle/HyperNet</p>
        
    </div>
</blockquote>
        
    

<p>While it would be cool to look at XMOS chips, AES50, or even StageConnect now that it's <a target="_blank" href="https://github.com/OpenMixerProject/StageConnect">open-source</a> thanks to Chris, I don't think I'll touch audio stuff for a while now. However, I'm nowhere near done with live production tools. I've been playing with intercoms ☎️ (think ClearCom, RTS, …) for a few years now, and I've already invested quite a lot of time (and money) into making open-source analog and digital prototypes!</p>

 ]]></content:encoded></item><item><title>Ultranet adventures part 1: Working prototype!</title><description> &lt;p>Deep-diving into Ultranet and re-implementing both a receiver and transmitter on FPGA.&lt;/p></description><link>https://blog.thestaticturtle.fr/ultranet-adventures-part-1/</link><guid>https://blog.thestaticturtle.fr/ultranet-adventures-part-1/</guid><category> Electronics</category><category> Open source</category><category> Diy</category><category> Audio</category><category> Concerts</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 01 May 2025 16:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/ultranet-adventures-part-1/images/cover_hu2afa10f8a589ef2dc4835d3a16e1829f_380628_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/ultranet-adventures-part-1/images/cover_hu2afa10f8a589ef2dc4835d3a16e1829f_380628_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Ultranet adventures part 1: Working prototype!</h1>
<span class="subtitle"><p>Deep-diving into Ultranet and re-implementing both a receiver and transmitter on FPGA.</p></span>
<br>

    <img class="" src='/ultranet-adventures-part-1/images/cover_hu2afa10f8a589ef2dc4835d3a16e1829f_380628_1350x900_fit_q80_box.jpg' alt="Ultranet adventures part 1: Working prototype!"/>

<hr>

<blockquote class="warning">
    
    
        <p class="warning-text">
This is a stupidly long article, it details the different phases thoroughly, you'll need probably some time to really read it. <br>Demo video is available at the <a href="#its-working-encore">end</a>!
</p>
    
</blockquote>
<h2 id="why">
    Why 
    
    <a class="header-link" href="#why">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>In my last article about the <a target="_blank" href="/diy-opensource-bidirectional-sdi-to-fiber-converter/">open-source SDI to fiber converter</a>, I briefly talked about my brand new MPO-12 cable with 12 OM3 fibers 🔦.
I've also hinted that I might use the two spare fibers I had for audio 🔊.</p>
<p>These audio channels will be used mostly as side-channels, stuff like LTC time code, backup sound pickup, and (maybe) intercom</p>
<p>When I started this project, there were four &quot;mainstream&quot; ways I thought I could tackle this:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/S/PDIF">S/PDIF (Sony/Philips Digital Interface)</a> (or it's professional big brother <a target="_blank" href="https://en.wikipedia.org/wiki/AES3">AES/EBU</a>)<br>It's a digital audio interface transmitted over various connectors, such as (plastic) fiber-optic cables. This would be the most obvious option, S/PDIF can already be transmitted over fiber so it wouldn't be that difficult to adapt it to OM3 with an SFP module.
<ul>
<li>The main upside is that there are plenty of cheap modules that already exist to convert analog to digital and vice versa</li>
<li>The main downside is that it is only two channels per link 🤔
<br><br></li>
</ul>
</li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/ADAT_Lightpipe">ADAT Lightpipe</a><br>Lightpipe uses the same connection hardware as S/PDIF: fiber optic cables (hence its name) to carry data, with TOSLINK connectors and optical transceivers at either end. The main difference stems from the fact that ADAT supports up to 8 audio channels at 48 kHz, 24 bits.
<ul>
<li>The main upside is that 8 channels is pretty good</li>
<li>The main downside is that from some quick research, there isn't a recent IC or implementation that exists and devices that implement the protocol cost too much for my use.
<br><br></li>
</ul>
</li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/MADI">Multichannel Audio Digital Interface (MADI) / AES10</a> <br>This interface supports serial digital transmission over coaxial cable or fiber-optic lines of 28, 56, 32, or 64 channels and sampling rates to 96 kHz and beyond with an audio bit depth of up to 24 bits per channel.
<ul>
<li>The main upside is the number of channels and the fact that it already runs over multimode fiber 🔦.</li>
<li>The main downside is that it's even worse than ADAT, there isn't a recent/easy implementation that exists and devices that implement the protocol have a prohibitive cost for hobbyist use.
<br><br></li>
</ul>
</li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Dante_%28networking%29">Dante</a> / <a target="_blank" href="https://en.wikipedia.org/wiki/AES67">AES67</a><br>This is a combination of software, hardware, and network protocols that delivers uncompressed, multichannel, low-latency digital audio over a standard Ethernet network 🌐 using Layer 3 IP packets.
<ul>
<li>The main upside is that there are plenty of channels (max 512) 📈, it's easy to <strong>use</strong>, and it's implemented on countless devices</li>
<li>The main downside is that implementing it to get low latency ⏱ will be a nightmare</li>
</ul>
</li>
</ul>
<p>I didn't really like any of these 😕, I did try to build a prototype ADAT board based on the AL1401/A1L402 ICs, but I didn't have any luck 🙁.</p>
<p>I did dip my toes in AES67 for another project, but access to the full specification as a hobbyist is a bit complicated and the overall setup required to get it working at all without dealing with latency requires a lot of work ⏳.</p>
<p>Then the universe dropped this gem 💎 from Christian Nöding:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/c7VGjq9yp8g"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<p>This is what kickstarted the whole thing. From the video, it seemed that Ultranet (a protocol that I have heard of but never used), is somewhat based on two AES3 signals each containing 8 channels.
This would mean either 8 channels bidirectional or 16 uni-directional channels on two fiber 🤩.
Moreover, I do have (limited) access to hardware that can send and receive Ultranet so I can easily test my implementation.
But the most important thing is that Christian proved it was possible to do it 🥳!</p>
<p><em>Note: for the story's sake, events, and discoveries aren't necessarily in chronological order.</em></p>
<h2 id="research">
    Research 
    
    <a class="header-link" href="#research">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As always for big projects, I started by doing some research 📜, this part also contains discoveries and light-bulb moments I had <strong>during</strong> the project.</p>
<h3 id="aesebu">
    AES/EBU 
    
    <a class="header-link" href="#aesebu">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I mentioned that Ultranet is based on AES3, also known as AES/EBU so let's start there, how the hell does this work, and where do we start?</p>
<p>The <a target="_blank" href="https://www.aes.org/">Audio Engineering Society (AES)</a>, is a professional society 🏢 devoted exclusively to audio technology. Alongside other groups like the <a target="_blank" href="https://www.ebu.ch/">European Broadcasting Union (EBU)</a> 🎥 they standardize different technologies in the audio/broadcasting industry.</p>
<p>This article will talk exclusively about AES3. Newer standards are behind a paywall. Fortunately, AES3 is as older than me. It was first published in 1985 and was revised in 1992, 2003 and 2009. That means that we can find semi-recent leaked PDFs without much difficulty.</p>
<p>Moreover, there are a bunch of other documents linked to AES3 from which we can scoop up information:</p>
<ul>
<li><a target="_blank" href="https://tech.ebu.ch/docs/other/aes-ebu-eg.pdf">AES/EBU EG</a> - <code>Engineering Guidelines - (The AES/EBU audio interface)</code></li>
<li><a target="_blank" href="https://tech.ebu.ch/docs/tech/tech3250.pdf">EBU Tech 3250-2004</a> - <code>Specification of the digital audio interface (The AES/EBU interface)</code></li>
<li><a target="_blank" href="https://webstore.iec.ch/en/publication/62829">IEC60958</a> - <code>Digital audio interface - Part 1: General</code></li>
<li>AES-2id - <code>Guidelines for the use of the AES3 interface</code></li>
</ul>
<h4 id="electrical-signals">
    Electrical signals 
    
    <a class="header-link" href="#electrical-signals">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>AES3 can be transmitted over three main kinds of connections:</p>
<ul>
<li><strong>IEC60958 Type I</strong>: This defines a balanced, three-conductor, 110-ohm twisted pair cable with XLR connectors. Type I connections are most often used in professional installations and are considered the standard connector for AES3</li>
<li><strong>IEC60958 Type II</strong>: It defines an unbalanced electrical or optical interface for consumer electronics applications. This implementation is the one used by S/PDIF. S/PDIF and AES3 are interchangeable at the protocol level, but differ at the physical level (voltage / impedances).</li>
<li><strong>BNC connectors</strong>: AES/EBU signals can also be run using an unbalanced 75-ohm coaxial cable. The unbalanced version has a very long transmission distance, instead of the 150 meters maximum for the balanced version. The AES-3id standard defines a 75-ohm BNC electrical variant of AES3.</li>
</ul>
<p>From the <code>EBU Tech 3250-2004</code> document, we can get a bunch of information about the electrical characteristics of AES3, which boils down in my opinion to the most important being that the system needs an impedance of 110 Ohm ± 20% with an amplitude that lies between 2 and 7 V peak-to-peak.</p>
<h4 id="data-encoding">
    Data encoding 
    
    <a class="header-link" href="#data-encoding">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>AES3 was designed primarily to support stereo <a target="_blank" href="https://en.wikipedia.org/wiki/PCM">PCM</a> 📊 encoded audio in either <a target="_blank" href="https://en.wikipedia.org/wiki/Digital_audio_tape">DAT</a> format at 48 kHz 🎤 or <a target="_blank" href="https://en.wikipedia.org/wiki/CD">CD</a> format at 44.1 kHz 💿. Instead of using an elaborate scheme to support both rates, AES3 allows instead to be run at any rate 💪, and encodes the clock and the data together using <a target="_blank" href="https://en.wikipedia.org/wiki/Biphase_mark_code">biphase mark code (BMC)</a>.</p>
<p>Biphase mark code, also known as differential Manchester encoding, is a method to transmit data in which the data 💾 and clock 🕓 signals are combined to form a single two-level self-synchronizing data stream. Each data bit is encoded by a presence or absence of signal level transition in the middle of the bit period (known as time slot for AES3), followed by the mandatory level transition at the beginning of the period. This also means that by design the encoding is insensitive to an inversion of polarity 🔀.</p>
<p>There are two variants of BMC:</p>
<ul>
<li>Transition on a 1 which is the one used for AES3</li>
<li>Transition on a 0 which is irrelevant for this project</li>
</ul>
<p>Here is an example diagram representing how the signal behave, there is a transition on each solid line plus a transition on the dotted line if the bit is a one:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/bmc-encoding.drawio.png" >
            <img alt="Differential_manchester_encoding" src="/ultranet-adventures-part-1/diagrams/bmc-encoding.drawio.png" />
        </a>
        <figcaption>Differential Manchester Encoding (AKA Biphase mark code)</figcaption>
    </figure>

</p>
<p>Differential Manchester encoding has the following advantages:</p>
<ul>
<li>A transition is guaranteed at least once every bit, for robust clock recovery.</li>
<li>If the high and low signal levels have the same magnitude with opposite polarity, the average voltage around each unconditional transition is zero. Zero DC bias reduces the necessary transmitting power and minimizes the electromagnetic noise produced by the transmission line.</li>
</ul>
<p>All of these positive features come at the expense of clock speed 🏃‍♂️. BMC needs a clock twice as fast as the data rate to encode the bit stream</p>
<h4 id="blocks-frames-time-slots">
    Blocks, frames, time slots 
    
    <a class="header-link" href="#blocks-frames-time-slots">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Now that we know how to read the bits, let's talk about what those bits actually mean!</p>
<p>AES3 is composed of what is called audio blocks, these audio blocks are composed of 192 frames, each frame contains 2 subframes, which in turns, contain 32 time slots.</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/aes3-block-structure.drawio.png" >
            <img alt="AES3 Block structure" src="/ultranet-adventures-part-1/diagrams/aes3-block-structure.drawio.png" />
        </a>
        <figcaption>AES3 Block structure</figcaption>
    </figure>

</p>
<p>A subframe is composed of:</p>
<table>
<thead>
<tr>
<th>Time slot</th>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0–3</td>
<td>Preamble</td>
<td>A synchronization preamble</td>
</tr>
<tr>
<td>4–7</td>
<td>Auxiliary sample</td>
<td>A low-quality auxiliary channel used as specified in the channel status word.</td>
</tr>
<tr>
<td>8–27</td>
<td>Audio sample</td>
<td>Audio sample stored MSB last. It can be extended to use the auxiliary sample to increase quality</td>
</tr>
<tr>
<td>28</td>
<td>Validity (V)</td>
<td>Unset if the audio data is correct and suitable for D/A conversion.</td>
</tr>
<tr>
<td>29</td>
<td>User data (U)</td>
<td>Forms a serial data stream for each channel.</td>
</tr>
<tr>
<td>30</td>
<td>Channel status (C)</td>
<td>Bits from each subframe of an audio block are collated, giving a 192-bit channel status word.</td>
</tr>
<tr>
<td>31</td>
<td>Parity (P)</td>
<td>Even parity bit for detection of errors in data transmission. Excludes preamble; Bits 4–31 need an even number of ones.</td>
</tr>
</tbody>
</table>
<p>
    <figure>
        <a target="_blank" href="diagrams/aes3-subframe.drawio.png" >
            <img alt="AES3 Subframe structure" src="/ultranet-adventures-part-1/diagrams/aes3-subframe.drawio.png" />
        </a>
        <figcaption>AES3 Subframe structure</figcaption>
    </figure>

</p>
<p>The preamble can be one of three values:</p>
<table>
<thead>
<tr>
<th style="text-align:center">Name</th>
<th style="text-align:center">Timeslot (Last was 0)</th>
<th style="text-align:center">Timeslot (Last was 1)</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">Z or B</td>
<td style="text-align:center">11101000</td>
<td style="text-align:center">00010111</td>
<td>Marks a word for channel A (left) and the start of an audio block</td>
</tr>
<tr>
<td style="text-align:center">X or M</td>
<td style="text-align:center">11100010</td>
<td style="text-align:center">00011101</td>
<td>Marks a word for channel A (left), besides at the start of an audio block</td>
</tr>
<tr>
<td style="text-align:center">Y or W</td>
<td style="text-align:center">11100100</td>
<td style="text-align:center">00011011</td>
<td>Marks a word for channel B (right).</td>
</tr>
</tbody>
</table>
<h4 id="channel-status-word">
    Channel status word 
    
    <a class="header-link" href="#channel-status-word">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Between the AES3 and S/PDIF standards, the contents of the 192-bit channel status word differ significantly, although they both standards agree that the first channel status bit distinguishes between the two 🤝. In the case of AES3, the standard describes, in detail, the function of each bit.</p>
<p>Broadly speaking, the channel status word indicates the type of data, has information about the clocks and various metadata such as channel origin/destination.</p>
<p>I won't go into more detail in this article mainly because it's mostly irrelevant for Ultranet 🧐.
For AES3, you can find the full format in the <code>EBU Tech 3250-2004</code> document. For consumer S/PDIF it's a bit more blurry but the English Wikipedia article has a nice table 📅.</p>
<h4 id="practical-example">
    Practical example 
    
    <a class="header-link" href="#practical-example">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>That's a lot to take in, so let's look at a practical example from my logic analyzer:</p>
<p>
    <figure>
        <a target="_blank" href="images/DSView_2025-03-19_16-07-30_cee0854c-4d4e-4325-bd1f-56633cf9dca8.png" >
            <img alt="Logic analyzer capture of an AES3 signal" src="/ultranet-adventures-part-1/images/DSView_2025-03-19_16-07-30_cee0854c-4d4e-4325-bd1f-56633cf9dca8.png" />
        </a>
        <figcaption>Logic analyzer capture of an AES3 signal</figcaption>
    </figure>

</p>
<p>Let's see what we can figure out:</p>
<ul>
<li>This subframe starts with the B preamble, this tells us that it's the <strong>start of an audio block</strong> and that it's the <strong>left channel</strong> left.</li>
<li>We are going to consider that the auxiliary bits are used for audio. This gives us <code>0xadffff</code>, if we change the bit order from LSB-first (AES3) to MSB-first 🔀 (what is generally used for audio) the 24bit <strong>audio data is <code>0xffffb5</code></strong>.</li>
<li>Even tho we have data the validity bit tells us that <strong>this frame is invalid</strong> 🛑 and that it shouldn't be played</li>
<li>Then comes the user bit with an undefined structure.</li>
<li>There is the channel status word, this tells us that the first bit of the word is <strong>a 0 indicating S/PDIF data</strong></li>
<li>Finally, the parity bit <strong>is 0</strong> because the number of <strong>asserted bits in the 4-30 range</strong> already are an <strong>even number of 1s</strong> 🧮</li>
</ul>
<p>And that's it really, the M preamble will then be used for the rest of the left channel subframes and the W will be used for the right channel.
Then after 384 subframe, there will be another B preamble signaling a new block.</p>
<h3 id="ultranet">
    Ultranet 
    
    <a class="header-link" href="#ultranet">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Now, how does Ultranet differ from AES3?</p>
<blockquote class="warning">
    
    
        <p class="warning-text">As there is no official documentation that is publicly available (or any leaks for that matter), everything that not straight out of a product sheet is informed speculation, reverse-engineering and trial & error and might not reflect exactly the actual protocol</p>
    
</blockquote>
<h4 id="what-we-know-from-product-sheets">
    What we know from product sheets: 
    
    <a class="header-link" href="#what-we-know-from-product-sheets">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>I read through the datasheets/quick guides from several Ultranet compatible devices from Behringer and its subsidiaries. Here is what is always present and important to this project:</p>
<ul>
<li><strong>Digital Processing</strong>
<ul>
<li><strong>A/D conversion:</strong> 24-bit, 44.1 kHz / 48 kHz sample rate</li>
<li><strong>Latency:</strong> &lt;0.9 ms (from <a target="_blank" href="https://www.behringer.com/product.html?modelCode=0609-AAA">P16-I</a> to <a target="_blank" href="https://www.behringer.com/product.html?modelCode=0609-AAP">P16-HQ</a>)</li>
</ul>
</li>
<li><strong>System</strong>
<ul>
<li><strong>Signal:</strong> 16 channels, plus bus-power</li>
<li><strong>Power</strong>:
<ul>
<li>P16-M consumes max. 5W</li>
<li>P16-D consumes max. 40W</li>
</ul>
</li>
</ul>
</li>
<li><strong>Cabling</strong>
<ul>
<li><strong>Connectors:</strong> RJ45</li>
<li><strong>Cables:</strong> Shielded CAT5</li>
<li><strong>Cable length:</strong> max. 246 ft (ca. 75 meters) recommended</li>
</ul>
</li>
</ul>
<p>Apart from the channel count, given the audio format &amp; latency plus the fact that the signal runs over CAT5, it sounds a lot like AES3 🤔. The product sheet also tells us that power is running on the same cable somehow.</p>
<h4 id="probing-and-reverse-engineering-the-electronics-">
    Probing and reverse-engineering the electronics 🍑 
    
    <a class="header-link" href="#probing-and-reverse-engineering-the-electronics-">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>As I said before, the work that Christian did is what kickstarted this project. At this point, he already published his video and figured out that as Ultranet uses generic CAT5 cables, which means they most likely also use standard wiring.</p>
<p>As it turns out (and we'll see why later in the protocol section), Ultranet does not send 16 channels down a single stream. Instead, it sends 8 channels over two separate (but synchronized) streams.
That means 2 out 4 pairs are used for audio and leaves 2 pairs for power, which looks a lot like 100BASE-T with POE mode B 😅.</p>
<table>
<thead>
<tr>
<th style="text-align:center">Pin</th>
<th style="text-align:center">Pair</th>
<th>Use for 100BASE-T<br>with POE mode B</th>
<th>Use for Ultranet</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">1</td>
<td style="text-align:center">3</td>
<td>📤 TX+</td>
<td>🔊 CH_1-8_+</td>
</tr>
<tr>
<td style="text-align:center">2</td>
<td style="text-align:center">3</td>
<td>📤 TX-</td>
<td>🔊 CH_1-8_-</td>
</tr>
<tr>
<td style="text-align:center">3</td>
<td style="text-align:center">2</td>
<td>📥 RX+</td>
<td>🔊 CH_1-8_+</td>
</tr>
<tr>
<td style="text-align:center">4</td>
<td style="text-align:center">1</td>
<td>🔌 48VDC</td>
<td>🔌 15VDC</td>
</tr>
<tr>
<td style="text-align:center">5</td>
<td style="text-align:center">1</td>
<td>🔌 48VDC</td>
<td>🔌 15VDC</td>
</tr>
<tr>
<td style="text-align:center">6</td>
<td style="text-align:center">2</td>
<td>📥 RX-</td>
<td>🔊 CH_1-8_+</td>
</tr>
<tr>
<td style="text-align:center">7</td>
<td style="text-align:center">4</td>
<td>🔌 48VDC</td>
<td>🔌 15VDC</td>
</tr>
<tr>
<td style="text-align:center">8</td>
<td style="text-align:center">4</td>
<td>🔌 48VDC</td>
<td>🔌 15VDC</td>
</tr>
</tbody>
</table>
<p>Earlier I briefly talked about the AES3 electrical specifications. Again the <code>EBU Tech 3250-2004</code> document comes to the rescue, there is a whole section on differential pairs and how AES3 should be wired 🔌:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/aes-specs.drawio.png" >
            <img alt="Simplified example of the AES3 circuit" src="/ultranet-adventures-part-1/diagrams/aes-specs.drawio.png" />
        </a>
        <figcaption>Simplified example of the AES3 circuit</figcaption>
    </figure>

</p>
<p>Here are a few other important characteristics that have to be respected:</p>
<blockquote>
<p>The interconnecting cable shall be balanced and screened (shielded) with nominal characteristic impedance of 110 Ohms at frequencies from 0.1 to 128 times the maximum frame rate.</p>
</blockquote>
<blockquote>
<p>The line driver shall have a balanced output with an internal impedance of 110 Ohm ± 20%, at frequencies from 0.1 to 128 times the maximum frame rate when measured the output at terminals.</p>
</blockquote>
<blockquote>
<p>The signal amplitude shall lie between 2 and 7 V peak-to-peak, when measured across a 110 Ohm resistor connected to the output terminals, without any interconnecting cable present.</p>
</blockquote>
<blockquote>
<p>Any common mode component at the output of the equipment shall be more than 30 dB below the signal at frequencies from DC to 128 times the maximum frame rate.</p>
</blockquote>
<p>That's a bunch of information, and there is even more in the document. But to be honest, for a prototype, I just YOLO-ed the design 🤡 based on a quick datasheet read through and the work of Christian.</p>
<p>So to summarize, the important things are:</p>
<ul>
<li>110 Ohms ± 20%</li>
<li>Between 2 and 7 volts peak-to-peak</li>
<li>The AES3 bit stream is 6.144 Mbit/s for 48Khz 2ch and if we assume that Ultranet is 8ch/stream, that is 24.576 Mbit/s</li>
<li>Behringer seem to skirt the &quot;this breaks the spec&quot; line of every specification they based Ultranet upon.</li>
</ul>
<p>So we can safely assume 🧐 that they are using a standard line driver running at 5V over a generic Ethernet pulse transformer which has a typical impedance of 100 Ohms (which fits the tolerance)</p>
<p>During his project, Christian made a small PCB to receive Ultranet, he used the <a target="_blank" href="https://www.mouser.fr/datasheet/2/643/belfs08419_1-2290057.pdf">SI-52008-F</a> an RJ-45 connector with integrated magnetics and POE capability. This connector is then wired to the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/am26lv32.pdf">AM26LV32</a>, a <code>Low-Voltage, High-Speed Quadruple Differential Line Receiver</code> that can handle up to 32MHz data rates, can receive 5V signals and outputs 3.3V.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/chrome_2025-03-23_14-24-40_2037adcc-535c-4214-934e-fe13cc91facc.png">
                            <img src="/ultranet-adventures-part-1/images/chrome_2025-03-23_14-24-40_2037adcc-535c-4214-934e-fe13cc91facc.png" alt="Christian&#39;s PCB (Top)">
                        </a>
                        
                            <figcaption>Christian&#39;s PCB (Top)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/chrome_2025-03-23_14-23-39_c403c642-afb7-4cc8-b862-0bc40edab97f.png">
                            <img src="/ultranet-adventures-part-1/images/chrome_2025-03-23_14-23-39_c403c642-afb7-4cc8-b862-0bc40edab97f.png" alt="Christian&#39;s PCB (Bottom)">
                        </a>
                        
                            <figcaption>Christian&#39;s PCB (Bottom)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/chrome_2025_03_20_11-23-59_MV5yxRStla.png">
                            <img src="/ultranet-adventures-part-1/images/chrome_2025_03_20_11-23-59_MV5yxRStla.png" alt="AM26LV32 Logic diagram">
                        </a>
                        
                            <figcaption>AM26LV32 Logic diagram</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>This seems pretty good, but writing this article I did notice that the common-mode range is 2 volts under the AES3 spec, but I doubt it's going to cause massive issues 🧨 for the prototype and I probably won't be swapping it for something else for part 2.</p>
<p>The <a target="_blank" href="https://www.ti.com/lit/ds/symlink/am26lv32.pdf">AM26LV32</a> also has a brother, the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/am26lv31.pdf">AM26LV31</a> a <code>Low-Voltage High-Speed Quadruple Differential Line Driver</code> which has just about the same specs but goes into the other direction:</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025_03_20_11-27-50_OS4d4oYBDK.png" >
            <img alt="AM26LV31 Logic diagram" src="/ultranet-adventures-part-1/images/chrome_2025_03_20_11-27-50_OS4d4oYBDK.png" />
        </a>
        <figcaption>AM26LV31 Logic diagram</figcaption>
    </figure>

</p>
<blockquote>
<p>While writting this article I say things as tho they are obvious and the only option. Truth is until very late into the project I was extremly unsure about the electronics. At the time, I was struggling getting a signal in/out from real hardware and I was suspecting these circuit more and more.</p>
</blockquote>
<p>All of this uncertainty lead me down the path of trying to reverse-engineering the electrical side of a proprietary protocol with nothing but Google Images 🖼. After much research, I stumbled onto the <a target="_blank" href="https://www.thomann.fr/klark_teknik_dm80_ultranet.htm">Klark Teknik DM80-Ultranet</a> an Ultranet expansion card for the <a target="_blank" href="https://www.klarkteknik.com/product.html?modelCode=0829-AAC">DM8000</a>. What was fascinating was the very nice and high resolution 🔍 top view of the pcb.</p>
<p>After loading the image into Gimp, I began tracing out connections and with the help of <a target="_blank" href="https://smd.yooneed.one/">The ultimate SMD marking codes database</a>, I managed to get the information I was looking for:</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/15667910.jpg">
                            <img src="/ultranet-adventures-part-1/images/15667910_hu01acd6520ccc04dd14d51151c4b52bd1_2457213_0x720_resize_q90_h2_box.webp" alt="Klark Teknik DM80-Ultranet">
                        </a>
                        
                            <figcaption>Klark Teknik DM80-Ultranet</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/ultranet_hardware_2.png">
                            <img src="/ultranet-adventures-part-1/images/ultranet_hardware_2_hu470ba9d24620936afc44c24a43ed2b65_2246805_0x720_resize_q90_h2_box_3.webp" alt="DM80-Ultranet bottom side (Power)">
                        </a>
                        
                            <figcaption>DM80-Ultranet bottom side (Power)</figcaption>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            
                
                
                    
                    
                
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/ultranet_hardware_1.jpg">
                            <img src="/ultranet-adventures-part-1/images/ultranet_hardware_1_huaae2dce9d337a86beb67fae81a481182_988118_0x720_resize_q90_h2_box.webp" alt="DM80-Ultranet top side (Signal)">
                        </a>
                        
                            <figcaption>DM80-Ultranet top side (Signal)</figcaption>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>The AES3 signals both go into a <a target="_blank" href="https://www.ti.com/lit/ds/symlink/sn74lvc1g04.pdf">SN74LVC1G04</a> <code>Single Inverter Gate</code> and a <a target="_blank" href="https://www.ti.com/lit/ds/symlink/sn74lvc1g125.pdf">SN74LVC1G125</a> <code>Single Bus Buffer Gate</code> which gives a 5V differential signal. It then goes into what I assume to be filters, a protection diode, and a common mode choke before going into either, the connector directly or through magnetics 🧲 (we can only guess here, but I think it goes straight to the connector).</p>
<p>The input circuitry goes from the connector (again, maybe through magnetics, but I doubt it) through what I assume to be a common mode choke into what I guess is a pulse transformer and I didn't bother going further as I already had this working, and it worked for Dr. Nöding.</p>
<p>After this evening, I was confident that the implementation that we'll see later was correct enough to work 😎!</p>
<h4 id="reverse-engineering-the-protocol">
    Reverse-engineering the protocol 
    
    <a class="header-link" href="#reverse-engineering-the-protocol">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>So how would you send 16 channels of digital audio down a CAT5 cable?</p>
<p>An important thing to remember is that while is its own unique thing, Ultranet is based on existing protocols and from what I've seen, they try not to break ⛓ them too much.</p>
<p>From different photos of main boards of products that implement Ultranet, we can see a recurring pattern, there always seem to be two of the same ICs, the <a target="_blank" href="https://media.digikey.com/pdf/Data%20Sheets/AKM%20Semiconductor%20Inc.%20PDFs/AK4114.pdf">AK4114</a>. This IC is a <code>High Feature 192kHz 24bit Digital Audio Interface Transceiver</code> 🔊</p>
<blockquote>
<p>The AK4114 is a digital audio transceiver supporting 192kHz, 24bits. The channel status decoder supports both consumer and professional modes. The AK4114 can automatically detect a Non-PCM bit stream. When combined with the multi channel codec (AK4527B or AK4529), the two chips provide a system solution for AC-3 applications. The dedicated pins or a serial µP I/F can control the mode setting.</p>
</blockquote>
<p>Features include:</p>
<ul>
<li>AES3, IEC60958, S/PDIF, EIAJ CP1201 Compatible</li>
<li>Unlock &amp; Parity Error Detection</li>
<li>Validity Flag Detection</li>
<li>Up to 24bit Audio Data Format</li>
<li>Master Clock Outputs</li>
</ul>
<p>From all of this, it appears that it's a bog-standard AES3/SPDIF receiver. Which means that once again, Behringer didn't go too far into customizing the protocol 🔧.</p>
<p>So how do you fit 16 channels 🗜 into the 192kHz the chip supports?  Well, you don't, as I mentioned before, there are two chips. <br>
But wait, this still leaves 8 channels, so how do you do this? Well, 48Khz is for two channels, math tells us that <code>2 * 4 = 8</code> and that <code>48 * 4 = 192</code> that means it can fit, with some tinkering 🤔.</p>
<p>Okay, enough guessing: it would seem that Ultranet basically is AES3 running at 192Khz with the 8 channels multiplexed together.</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/ultranet-block-structure.drawio.png" >
            <img alt="Ultranet frame structure" src="/ultranet-adventures-part-1/diagrams/ultranet-block-structure.drawio.png" />
        </a>
        <figcaption>Ultranet frame structure</figcaption>
    </figure>

</p>
<p>You'll see later why I think that something fishy 🐟 is going on and that there is more to channel ordering than this, but it's the basic idea!</p>
<p>That leaves the content of those bits, are they different? Well, yes, somewhat!</p>
<ul>
<li>Because they use standard ICs the preambles need to be the same</li>
<li>As well as the audio data.</li>
<li>The validity bit seems to be inverted, which is a good call when you think about it.</li>
<li>The user bits seem to be unused</li>
<li>The channel bits are used but not idea for what, yet changing them seemed to have no audible effect.</li>
<li>And again, since they use standard ICs, the parity bit simply cannot change (which I discovered the hard way)</li>
</ul>
<p>And that's it, really. Behringer created a very elegant solution 😎 stretching existing specification/protocols to meet their needs and didn't completely reinvent the wheel!</p>
<h2 id="building-a-dev-board">
    Building a dev-board 
    
    <a class="header-link" href="#building-a-dev-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I decided that my first step would be to design a prototype development board 🦾 where I could easily explore different avenues before going straight into a final design. This approach allows me to test various configurations and functionalities without committing too much time or resources upfront.</p>
<p>Where to start? A microcontroller 📟 seems like a good choice, but dealing with the specific signals required for this project is impractical on a traditional MCU (not to mention potential latency issues). While it might be possible to implement everything using a standard MCU, it would most likely require a specialized digital interface chip (DIX) such as the AK4114. Given these constraints and my interest in exploring new technologies, this was the perfect project to finally start working with FPGAs.</p>
<p>A mention must also be given to the <a target="_blank" href="https://www.xmos.com/">XMOS series of ICs</a> ‼️. This is what Behringer is using in their Ultranet products (specifically the <a target="_blank" href="https://www.xmos.com/download/XL216-512-TQ128-Datasheet%281.16%29.pdf">XL216-512-TQ128</a> in the P16-M). XMOS chips are weird 🫠 (in a good way) they are multicore microcontrollers dedicated to audio processing which means they have many peripherals related to audio as well as reference implementations.</p>
<p>XMOS chips are something that I definitively want to try someday 🤔 but FPGAs have been on my radar for quite some time now, I never had a practical use case until now.</p>
<p>This project can be boiled down to shifting bits around, which is precisely what an FPGA does best! By using an FPGA, I can implement complex digital logic directly in hardware, which offers significant advantages over software-based solutions when it comes to speed.</p>
<p>However, as I never dealt with FPGAs before, this is project is a bit of a long shot 🏹, but for hobby projects I almost always work like that, so let's get started!</p>
<h3 id="but-which-fpga">
    But which FPGA 
    
    <a class="header-link" href="#but-which-fpga">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>FPGA characteristics are far and wide; they range from tiny FPGAs capable of running basic tasks to monstrous devices that can handle hundreds of gigabits of data per second 🌐.</p>
<p>Given my project requirements, I need something that is powerful enough to :</p>
<ul>
<li>Receive and transmit two AES3 streams at 192 kHz</li>
<li>Handle at least 4 stereo I2S inputs</li>
<li>Handle at least 4 stereo I2S outputs</li>
</ul>
<p>I also want something powerful enought in case I want to do fancier stuff like mixing/equalizer/filters in the future 🎚️.</p>
<p>So it mainly came down to what I could get on a devboard for cheap and fast, there are two options I considered:</p>
<ul>
<li><a target="_blank" href="https://docs.arduino.cc/hardware/mkr-vidor-4000/">Arduino MKR Vidor 4000</a>
<ul>
<li>This board uses the Intel Cyclone 10CL016 FPGA.</li>
<li>It offers an integrated ARM Cortex-M0+ microcontroller and a full suite of onboard peripherals, making it versatile for various applications.</li>
<li>The Cyclone 10CL016 is capable of handling moderate complexity tasks and has sufficient resources to manage the AES3 streams efficiently. <a target="_blank" href="https://blog.tempus-ex.com/pro-video-with-arduinos-an-intro-to-sdi-video-and-pcb-fab/">It can even do SDI with some help</a></li>
<li>It's what Dr. Nöding successfully used in his project.</li>
</ul>
</li>
<li><a target="_blank" href="https://wiki.sipeed.com/hardware/en/tang/Tang-Nano-9K/Nano-9K.html">SiPEED Tang Nano 9K</a>
<ul>
<li>This board uses the Gowin GW1NR-9 FPGA.</li>
<li>It provides a cost-effective solution with an integrated USB interface, making it easy to program and debug.</li>
<li>The GW1NR-9 FPGA is smaller but still offers enough logic resources for my project's requirements.</li>
<li>Thanks to its price it's a pretty popular option among hobbyists.</li>
</ul>
</li>
</ul>
<p>In the end, despite that the RTL I'm basing this project upon was successfully tested on the  Vidor 4000, I decided to proceed with the Tang Nano 9K due to its cost-effectiveness💸, ease of use, and adequate performance for my project's needs. This choice allows me to focus 🎯 on developing the core FPGA functionality without the unnecessary complications of having to use the Arduino IDE alongside the Quartus Prime IDE.</p>
<h3 id="io">
    I/O 
    
    <a class="header-link" href="#io">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As this is a devboard that I want to potentially re-use in other projects ♻️, I went all in and included a wide range of connectivity options that would make this board versatile enought.</p>
<p>Instead of implementing the 16 inputs and 16 outputs that Ultranet uses, which would render this already big pcb even bigger. I opted to implement only half ✂️, which is good enought for development purposes as I can still receive one of the AES3 streams in its entirety.</p>
<p>The second part of this project will probably have some way to get the full 16 channels, but I think that 8 is enough for now anyway 🤷‍♂️.</p>
<p>At the very beginning, I mentioned that the end goal of this project is to run it over fiber 🔦, so I decided to incorporate an SFP cage along with the appropriate line <a target="_blank" href="https://www.ti.com/product/SN65LVDT2/part-details/SN65LVDT2DBVR">driver</a> and <a target="_blank" href="https://www.ti.com/product/SN65LVDS1/part-details/SN65LVDS1DBVR">receiver</a>.</p>
<blockquote>
<p>If you want to read more about SFP/SFP+ modules you can read the chapter I wrote on it for my <a target="_blank" href="https://blog.thestaticturtle.fr/diy-opensource-bidirectional-sdi-to-fiber-converter/#sfp--sfp">3G-SDI to fiber project</a></p>
</blockquote>
<p>I also included footprints for DLT1150R and DLT1150T Toslink connectors 🔦. These optical audio connectors support up to 16 Mbps data transfer, so I can't pass Ultranet on them, but they provide an additional layer of flexibility 🤸‍♂️ for connecting various audio devices using the &quot;normal&quot; implementation of AES3. And who knows, maybe I'll implement ADAT someday.</p>
<p>Finally, the star ⭐ feature of this project is the inclusion of Ultranet transmit and receive ports with the isolation transformers and line driver/receiver discussed before.</p>
<p>As a last-minute touch, I added several status LEDs 💡 and three buttons on the 1.8v GPIO port of the tang 9k which I didn't want to use for anything else.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-21_23-51-11_a8d58eee-1db8-4792-a731-0e615417a9ff.png" >
            <img alt="Render of the devboard" src="/ultranet-adventures-part-1/images/_hu4a82b073d36524541181795a825ba15a_196309_8339dfc3f13a73aba73e8fcdfed3fe5c.webp" />
        </a>
        <figcaption>Render of the devboard</figcaption>
    </figure>

</p>
<h3 id="analog-domain">
    Analog domain 
    
    <a class="header-link" href="#analog-domain">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>When it comes to handling audio signals of the project 🔊, there are multiple options available. You can use dedicated DACs (Digital-to-Analog Converters) and ADCs (Analog-to-Digital Converters), or you can opt for CODECs (ADC+DAC combined generally with some additional features), each one having its advantages and disadvantages ⚖️.</p>
<p>However, in this case, using a codec is a terrible choice 👎. CODECs typically share the same reference clocks between the ADC and DAC part.
This doesn't work for my application because while I generate the master clock for the transmit side, the decoder recovers it from the AES3 stream. In theory, they are the same but in practice various factors will slightly influence the clock speed 🕝.</p>
<p>While I'm sure it's possible to implement some sort of sample rate synchronization, I opted for the much simpler option of using dedicated ADCs and DACs each with their clocks.</p>
<p>The analog to digital converter I choose is the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/pcm1808.pdf">PCM1808</a> 👈. This device is a high-quality single-ended, analog-input 24-bit ADC capable of operating at up to 96 kHz sample rate in stereo mode.</p>
<p>The digital to analog converter I went for is the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/pcm5102a.pdf">PCM5102A</a> 👈. This device is a high-performance audio stereo DAC with an integrated PLL (meaning I don't have to generate the master clock), capable of handling up to 384 kHz sample rates and supporting 32-bit PCM interfaces.</p>
<p>The choice of ICs came down to familiarity ✨ with the specific chips and ease of configuration. Given that both the PCM1808 and PCM5102A are well-documented and widely used components by both enthusiasts and professionals, they provide a reliable foundation for my project requirements.</p>
<p>Compared to other ICs, these are configured only by pin strapping 🔌 this means it's easier to troubleshoot, and I don't need to implement another protocol like I2C or SPI in the FPGA just to configure them.</p>
<h4 id="adc-pcm1808">
    ADC: PCM1808 
    
    <a class="header-link" href="#adc-pcm1808">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The schematic of the ADC is almost straight out of the typical application schematic provided in the datasheet. The datasheet does mention that in certain application an additional antialiasing filter could be required. But this is a prototype with the main goal of getting any sound at all 🔊, not necessarily a high-quality one.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-29_23-14-42_8353437c-cd1a-4421-9d1d-31f51fe5f33e.png" >
            <img alt="Schematic of the ADCs" src="/ultranet-adventures-part-1/images/chrome_2025-03-29_23-14-42_8353437c-cd1a-4421-9d1d-31f51fe5f33e.png" />
        </a>
        <figcaption>Schematic of the ADCs</figcaption>
    </figure>

</p>
<p>The <code>FMT</code> pin is tied to 3.3V for all ICs which tells the ADC to send data in the left-justified format. The other option would be I2S, neither format is the perfect as it depends on the implementation and simply changes the way the data is sent. Simply put, I2S mode offsets the data by one BCLK cycle which is a bit more annoying to deal with.</p>
<table>
<thead>
<tr>
<th>FORMAT NO.</th>
<th>FMT (Pin 12)</th>
<th>FORMAT</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Low</td>
<td>I2S, 24-bit</td>
</tr>
<tr>
<td>1</td>
<td>High</td>
<td>Left-justified, 24-bit</td>
</tr>
</tbody>
</table>
<p>The other two strapping pins (<code>MD1</code> &amp; <code>MD0</code>) control whether the IC operates in master or slave mode.
The schematic on top is the one from the second ADC which handles channel 3 &amp; 4. The first ADC is almost identical 🪞. It simply has additional test pads on these pins that allow me to easily change this behavior.</p>
<p>This leaves me the flexibility of not needing to implement the word clock &amp; bit clock dividers in the FPGA.</p>
<table>
<thead>
<tr>
<th>MD1 (PIN 11)</th>
<th>MD0 (PIN 10)</th>
<th>INTERFACE MODE</th>
</tr>
</thead>
<tbody>
<tr>
<td>Low</td>
<td>Low</td>
<td>Slave mode (256 FS, 384 FS, 512 FS autodetection)</td>
</tr>
<tr>
<td>Low</td>
<td>High</td>
<td>Master mode (512 FS)</td>
</tr>
<tr>
<td>High</td>
<td>Low</td>
<td>Master mode (384 FS)</td>
</tr>
<tr>
<td>High</td>
<td>High</td>
<td>Master mode (256 FS)</td>
</tr>
</tbody>
</table>
<p>In the end, I did use slave mode because of one very specific line of the datasheet that you could easily miss:</p>
<blockquote>
<p><strong>7.3.5.1.1 Master Mode</strong><br>
In master mode, BCK and LRCK work as output pins, timing which from the clock circuit of the PCM1808 device controls these pins. The frequency of BCK is constant at 64 BCK/frame.</p>
<p><strong>7.3.5.1.2 Slave Mode</strong><br>
In slave mode, BCK and LRCK work as input pins. The PCM1808 device accepts 64-BCK/frame or 48-BCK/frame format (only for a 384-fS system clock), not 32-BCK/frame format.</p>
</blockquote>
<p>Typically, you would expect a 24 bit ADC to use 48 cycles but the PCM1808 offers the possibility of using a 64 cycle bit clock 🎉. This significantly simplifies the clock generation and also allocates me a bit of time to process the data because I get an &quot;extra&quot; 8 clock cycle per channel to move data around.</p>
<h4 id="dac-pcm5102a">
    DAC: PCM5102A 
    
    <a class="header-link" href="#dac-pcm5102a">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Once again, the schematic of the DAC is almost straight out of the typical application schematic provided in the datasheet 📋.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-29_23-14-10_7d0331ff-3852-410a-b5e0-3ba9e8513d5f.png" >
            <img alt="Schematic of the DACs" src="/ultranet-adventures-part-1/images/chrome_2025-03-29_23-14-10_7d0331ff-3852-410a-b5e0-3ba9e8513d5f.png" />
        </a>
        <figcaption>Schematic of the DACs</figcaption>
    </figure>

</p>
<p>The <code>FMT</code> pin is (unfortunately 🤦‍♂️) set to use the I2S format.</p>
<p>Additionally, this DAC has extra pins like the <code>FLT</code> pin which controls the filter used (normal latency <a target="_blank" href="https://en.wikipedia.org/wiki/Finite_impulse_response">FIR</a> or low latency <a target="_blank" href="https://en.wikipedia.org/wiki/Infinite_impulse_response">IIR</a>) or the <code>DEMP</code> pin which enables the de-emphasis when used at sampling rate of 44.1 kHz.</p>
<p>It also has the <code>XSMT</code> to allow external devices to mute the DAC with a simple binary signal 🔇. The datasheet provides a good example of how to use this pin to mute the signal in case of an &quot;unplanned shutdown&quot; or under-voltage condition which will avoid (or at least reduce) the dreaded &quot;pop&quot; sound 💥.</p>
<h4 id="pcb">
    PCB 
    
    <a class="header-link" href="#pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Just like for the schematic, the PCB layout is practically the same as the datasheets.</p>
<p>You will notice the fuckload of test points 🚀 (some being outside the screenshot and always accompanied by their ground point 🌱). This is used to easily connect my logic analyzer and facilitate debugging.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-21_22-20-42_7eb74f57-b6d9-45b3-abed-5aa626c6f3ed.png" >
            <img alt="Render of the ADC/DAC part of the devboard" src="/ultranet-adventures-part-1/images/chrome_2025-03-21_22-20-42_7eb74f57-b6d9-45b3-abed-5aa626c6f3ed.png" />
        </a>
        <figcaption>Render of the ADC/DAC part of the devboard</figcaption>
    </figure>

</p>
<h3 id="clocks">
    Clocks 
    
    <a class="header-link" href="#clocks">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Clock signals ⏱️ are the heartbeat of any digital circuit, providing a consistent timing reference that ensures all operations within the system occur in harmony. Without precise clock signals, systems would operate chaotically, leading to data corruption and unreliable performance.</p>
<p>The Tang 9k has a 27MHz crystal 🏃‍➡️ on board and two PLLs on-chip, which could theoretically be used to generate the &quot;master-clock&quot; needed for the audio ICs.
However, after conducting preliminary tests before designing the PCB, I found it challenging 😵‍💫 to achieve precise clock generation from these on-chip resources. The internal PLLs did not provide the exact frequencies required for audio processing requirements, and configuring them was a bit complex.</p>
<p>Given that I'm lazy 🥱, at some point I simply didn't want to deal with this anymore, I decided to use a dedicated clock generation chip, specifically the <a target="_blank" href="https://www.ti.com/lit/ds/symlink/pll1707-q1.pdf">PLL1707</a>.</p>
<p>I can't stress how awesome this chip is 🤩. It's a perfect example of &quot;don't re-invent the wheel&quot;. For a few bucks more, it's an excellent choice to offload the generation of a precise and stable clock signal, especially since it's specifically designed for audio applications. One of its key features is its ability to generate frequencies of 512 (up to 768 actually) times the sampling frequency, which means it can produce the 24.576 MHz clock signal (48kHz * 512) needed for my project.</p>
<p>For internal operations within the FPGA that require faster speeds, instead of deriving from the 27MHz clock, I utilized this 24.576 MHz signal and multiplied it by 10 with a PLL to achieve higher-frequency clock signals for internal processes 🧩.</p>
<h4 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>By now you should get the idea. I read the datasheet and copied and pasted the schematic 😅. I'm kidding of course, generally I read it through at least once before drawing it 🤡.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-30_00-46-45_dc090ce0-65c0-4d49-b848-d6e82b597c5e.png" >
            <img alt="Schematic of the PLL1707" src="/ultranet-adventures-part-1/images/chrome_2025-03-30_00-46-45_dc090ce0-65c0-4d49-b848-d6e82b597c5e.png" />
        </a>
        
    </figure>

</p>
<p>The chip is configured to use the &quot;Double sampling rate&quot; mode (<code>SR</code> pin) for a 96Khz sampling frequency (<code>FS1</code> &amp; <code>FS2</code> pins) plus <code>CSEL</code> set to high. This configuration gives me the following clocks:</p>
<table>
<thead>
<tr>
<th>Clock</th>
<th>Frequency</th>
</tr>
</thead>
<tbody>
<tr>
<td>SCKO0</td>
<td>33.8688 MHz</td>
</tr>
<tr>
<td>SCKO1</td>
<td>24.576 MHz</td>
</tr>
<tr>
<td>SCKO2</td>
<td>24.576 MHz</td>
</tr>
<tr>
<td>SCKO3</td>
<td>36.864 MHz</td>
</tr>
<tr>
<td>MCKO1</td>
<td>27 MHz</td>
</tr>
<tr>
<td>MCKO2</td>
<td>27 MHz</td>
</tr>
</tbody>
</table>
<h4 id="pcb-1">
    PCB 
    
    <a class="header-link" href="#pcb-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Unfortunately, the datasheet doesn't provide a typically pcb layout 😕, thankfully this IC is simple to route so it wasn't a big issue.
Again, there are a bunch of test points to facilitate debugging.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2025-03-30_04-51-24_33822486-2869-4f0c-981e-7a885210ef3d.png" >
            <img alt="Render of the clock of the devboard" src="/ultranet-adventures-part-1/images/chrome_2025-03-30_04-51-24_33822486-2869-4f0c-981e-7a885210ef3d.png" />
        </a>
        <figcaption>Render of the clock of the devboard</figcaption>
    </figure>

</p>
<h3 id="board-bring-up--mistakes">
    Board bring-up &amp; mistakes 
    
    <a class="header-link" href="#board-bring-up--mistakes">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The bring-up ⚡is the process involving initial testing the board to ensure everything functions correctly. This includes setting up power, verifying analog connections, and ensuring digital 📱interfaces work as intended.</p>
<p>When I first plugged my logic analyzer into the board, I noticed that there was absolutely no activity on the data lines of the analog side 😵. This is weird because the master clock (and the derived word &amp; bit clock) were present, and the ADC should be autonomous 🤖.</p>
<p>Turns out, I connected the 3.3VA and 5VA supply lines to the ADCs and DACs, but I completely forgot to actually supply power to these connections 🔌 which is of course, essential for the chip to power up. Thankfully, as I had plastered the board with test points and 0-ohm resistors, the fix was relatively effortless 👌.</p>
<p>During the first reception test I noticed that while I was sending data and the right clocks to the DAC, I wasn't getting any audio 🔇. It turns out that the XSMT pin cannot be left floating, so I had to add a pull-up. As luck had it, the XMST line ran directly next to the 3.3V supply, so I could simply scrape the solder mask and solder a small resistor. You wouldn't even know it's a bodge. 😅</p>
<p>Further down in the development, I also needed to alter the configuration of the PCM5102A from the I2S format to the left-justified format, which was a bit easier to deal with 😮‍💨.</p>
<p>Another less important issue is that in my haste to get his devboard out the door, I swapped 🔀 the left and right channels of the DACs.
Continuing with issues on the DAC side, I also messed up the output filters 🎛️ which reduced the audio quality quite a bit!</p>
<p>Apart from these relatively minor issues, everything surprisingly worked just fine.</p>
<p>
    <figure>
        <a target="_blank" href="images/DCS03992_Bodges.JPG" >
            <img alt="Fixed PCB" src="/ultranet-adventures-part-1/images/DCS03992_Bodges_hu47906d1e5e24a12e4c3c24d870bceb0f_1171155_0x720_resize_q90_h2_box.webp" />
        </a>
        <figcaption>&quot;Fixed PCB&quot;</figcaption>
    </figure>

</p>
<h2 id="fpga-implementation">
    FPGA implementation 
    
    <a class="header-link" href="#fpga-implementation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Ok, let's start writing code (well, for FPGAs it's called RTL).</p>
<p>When I started writing the implementation, I did not have access to any hardware that supports Ultranet, so I started by writing a blind implementation, meaning I wrote both the transmitter and receiver parts at the same time, validating that they were working by connecting them together and looking at logic analyzer captures.</p>
<p>I also foolishly didn't start a git repository right away because at the beginning I was &quot;just messing around&quot;. That means that the implementation shown here is mostly the final version for part 1.</p>
<h3 id="edge-detectors">
    Edge detectors 
    
    <a class="header-link" href="#edge-detectors">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Before we start I need to explain one of the building blocks I used a lot: edge detectors. They give a synchronous notification of an asynchronous edge. Synchronization is conventionally done with a two-stage shift-register that is clocked by the target domain's clock. The shift-register is then extended, and the values are compared with the last two signals:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/edge-detector.drawio.png" >
            <img alt="Example edge detector diagram" src="/ultranet-adventures-part-1/diagrams/edge-detector.drawio.png" />
        </a>
        <figcaption>Example edge detector diagram</figcaption>
    </figure>

</p>
<p>In layman's term, it lets me &quot;watch&quot; a signal which is not running at the same clock speed (or which isn't even a clock at all).</p>
<h3 id="transmitter">
    Transmitter 
    
    <a class="header-link" href="#transmitter">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Here is a pretty good overview of how the transmitter works:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/transmitter.drawio.png" >
            <img alt="Ultranet transmitter overview" src="/ultranet-adventures-part-1/diagrams/transmitter.drawio_hue7bf9d2ccf32be8f6d35bd34743fa8d3_451383_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h4 id="clock">
    Clock 
    
    <a class="header-link" href="#clock">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The <code>Clocks</code> block is responsible for deriving the required clock signals for different parts of the module based on the master clock. It takes the 24.576 MHz clock and generates the following clocks:</p>
<ul>
<li>AES3 bit clock running at 24.576 MHz (simply &quot;buffered&quot;)</li>
<li>AES3 word clock running at 192Khz</li>
<li>I2S Bit clock running at 3.072 MHz</li>
<li>I2S Word clock running at 48Khz</li>
</ul>
<p>The process of dividing a clock is simple to do, you simply need a counter. Here is for example the process for the bit clock:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#0a8;font-weight:bold">i2s_bit_clock</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(mclk)  <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>    <span style="color:#069;font-weight:bold">if</span>(rising_edge(mclk)) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        count_i2s_bclk <span style="color:#555">&lt;=</span> count_i2s_bclk <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        <span style="color:#069;font-weight:bold">if</span>(count_i2s_bclk <span style="color:#555">=</span> <span style="color:#f60">3</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>            i2s_bclk <span style="color:#555">&lt;=</span> <span style="color:#069;font-weight:bold">not</span> i2s_bclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>            count_i2s_bclk <span style="color:#555">&lt;=</span><span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><p>As I said, the aes3 bit clock can simply be &quot;buffered&quot; like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>aes_bclk <span style="color:#555">&lt;=</span> mclk;
</span></span></code></pre></div><h4 id="i2s-deserializer">
    I2S Deserializer 
    
    <a class="header-link" href="#i2s-deserializer">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The <code>I2S Quad deserializer</code> block is responsible for reading the bits of the serial audio data coming from the ADCs in sync with the different clock signals.
The special thing about this block is that it takes four different inputs and already integrates the left/right demuxer to output the eight different 24bit vectors.</p>
<p>The module starts with a few edge detectors, the processes run on the much higher +100Mhz clock. Here I am using the bit clock as an example, but they are mostly all the same, you can see the 3 shift register stages and the comparator:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">detect_bclk_edge</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        zbclk <span style="color:#555">&lt;=</span> bclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        zzbclk <span style="color:#555">&lt;=</span> zbclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        zzzbclk <span style="color:#555">&lt;=</span> zzbclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        <span style="color:#069;font-weight:bold">if</span> zzbclk <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">and</span> zzzbclk <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            bclk_pos_edge <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        <span style="color:#069;font-weight:bold">elsif</span> zzbclk <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">and</span> zzzbclk <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            bclk_neg_edge <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            bclk_pos_edge <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            bclk_neg_edge <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><p>Then there are two processes, the first one is the one that makes the counters tick and more generally, where the flow of data is &quot;managed&quot;.
The second one is reading the serial data on the positive edge of a bit clock and shifting it into the appropriate buffer when told so by the first process.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">detect_sample</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#069;font-weight:bold">if</span> bsync_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>            <span style="color:#09f;font-style:italic">-- Sync detected, reset every signal</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            bit_cnt <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            <span style="color:#069;font-weight:bold">if</span> lrck_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>                <span style="color:#09f;font-style:italic">-- Left/right clock edge detected this means new channel -&gt; reset the bit counter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                bit_cnt <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>            <span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                <span style="color:#09f;font-style:italic">-- Bit clock positive clock edge detected -&gt; increment the bit counter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                bit_cnt <span style="color:#555">&lt;=</span> bit_cnt <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>            <span style="color:#069;font-weight:bold">if</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>  	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>                <span style="color:#09f;font-style:italic">-- Bit clock negative clock edge detected -&gt;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>                <span style="color:#09f;font-style:italic">-- Only read the first 24 bits, check the counter and set the signal appropriatly</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>                <span style="color:#069;font-weight:bold">if</span> bit_cnt <span style="color:#555">=</span> <span style="color:#f60">0</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>                    has_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>                <span style="color:#069;font-weight:bold">elsif</span> bit_cnt <span style="color:#555">&gt;=</span> <span style="color:#f60">24</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                    has_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>            <span style="color:#09f;font-style:italic">-- Raise new_data at the end of the last bit, only for one clk cycle</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>            <span style="color:#069;font-weight:bold">if</span> bit_cnt <span style="color:#555">=</span> <span style="color:#f60">31</span> <span style="color:#069;font-weight:bold">and</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">and</span> lrclk <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>                new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>            <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>                new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>              <span style="color:#09f;font-style:italic">-- Output the data</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>            <span style="color:#069;font-weight:bold">if</span> lrck_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>                sample_out_ch_1_r <span style="color:#555">&lt;=</span> sample_ch_1_r_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>                sample_out_ch_2_r <span style="color:#555">&lt;=</span> sample_ch_2_r_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>                sample_out_ch_3_r <span style="color:#555">&lt;=</span> sample_ch_3_r_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>                sample_out_ch_4_r <span style="color:#555">&lt;=</span> sample_ch_4_r_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>            <span style="color:#069;font-weight:bold">if</span> lrck_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>                sample_out_ch_1_l <span style="color:#555">&lt;=</span> sample_ch_1_l_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>                sample_out_ch_2_l <span style="color:#555">&lt;=</span> sample_ch_2_l_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>                sample_out_ch_3_l <span style="color:#555">&lt;=</span> sample_ch_3_l_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>                sample_out_ch_4_l <span style="color:#555">&lt;=</span> sample_ch_4_l_buf;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span><span style="color:#0a8;font-weight:bold">get_data</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span>    <span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>        <span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">and</span> has_data <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>            <span style="color:#069;font-weight:bold">if</span> lrclk <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>                sample_ch_1_l_buf <span style="color:#555">&lt;=</span> sample_ch_1_l_buf(sample_ch_1_l_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_1_l_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata1;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>                sample_ch_2_l_buf <span style="color:#555">&lt;=</span> sample_ch_2_l_buf(sample_ch_2_l_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_2_l_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata2;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>                sample_ch_3_l_buf <span style="color:#555">&lt;=</span> sample_ch_3_l_buf(sample_ch_3_l_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_3_l_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata3;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>                sample_ch_4_l_buf <span style="color:#555">&lt;=</span> sample_ch_4_l_buf(sample_ch_4_l_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_4_l_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata4;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span>            <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61</span><span>                sample_ch_1_r_buf <span style="color:#555">&lt;=</span> sample_ch_1_r_buf(sample_ch_1_r_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_1_r_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata1;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62</span><span>                sample_ch_2_r_buf <span style="color:#555">&lt;=</span> sample_ch_2_r_buf(sample_ch_2_r_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_2_r_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata2;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">63</span><span>                sample_ch_3_r_buf <span style="color:#555">&lt;=</span> sample_ch_3_r_buf(sample_ch_3_r_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_3_r_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata3;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">64</span><span>                sample_ch_4_r_buf <span style="color:#555">&lt;=</span> sample_ch_4_r_buf(sample_ch_4_r_buf<span style="color:#309">&#39;high</span><span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">downto</span> sample_ch_4_r_buf<span style="color:#309">&#39;low</span>) <span style="color:#555">&amp;</span> sdata4;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">65</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">66</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">67</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">68</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h4 id="ultranet-muxer">
    Ultranet muxer 
    
    <a class="header-link" href="#ultranet-muxer">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The <code>Ultranet mux</code> block has a simple job, each time the AES3 word clock rises or falls, it needs to increment the channel counter and output the data for said channel. Additionally, it takes a &quot;copy&quot; of the input vectors each time it sees that <code>new_data</code> is rising.</p>
<p>Once again, the module starts with some edge detectors, but the main logic can be seen below. As you can see, it's pretty simple!</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span> (rising_edge(clk)) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#069;font-weight:bold">if</span> new_data_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>            <span style="color:#09f;font-style:italic">-- Buffer the sample for each input channel</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            ch1_buffer <span style="color:#555">&lt;=</span> ch1_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            ch2_buffer <span style="color:#555">&lt;=</span> ch2_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            ch3_buffer <span style="color:#555">&lt;=</span> ch3_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            ch4_buffer <span style="color:#555">&lt;=</span> ch4_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            ch5_buffer <span style="color:#555">&lt;=</span> ch5_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            ch6_buffer <span style="color:#555">&lt;=</span> ch6_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            ch7_buffer <span style="color:#555">&lt;=</span> ch7_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            ch8_buffer <span style="color:#555">&lt;=</span> ch8_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>            <span style="color:#09f;font-style:italic">-- Reset the channel counter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>            channel_cnt <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>; 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        <span style="color:#069;font-weight:bold">if</span> aes_lrck_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>		
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>            <span style="color:#09f;font-style:italic">-- Increment the channel counter on each pulse</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>            channel_cnt <span style="color:#555">&lt;=</span> channel_cnt <span style="color:#555">+</span> <span style="color:#f60">1</span>; 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>            <span style="color:#09f;font-style:italic">-- Output the corrsponding sample</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>            <span style="color:#069;font-weight:bold">if</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">0</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                ch_out <span style="color:#555">&lt;=</span> ch1_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>                ch_out <span style="color:#555">&lt;=</span> ch2_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">2</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>                ch_out <span style="color:#555">&lt;=</span> ch3_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">3</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>                ch_out <span style="color:#555">&lt;=</span> ch4_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">4</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>                ch_out <span style="color:#555">&lt;=</span> ch5_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">5</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>                ch_out <span style="color:#555">&lt;=</span> ch6_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">6</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>                ch_out <span style="color:#555">&lt;=</span> ch7_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>            <span style="color:#069;font-weight:bold">elsif</span> channel_cnt <span style="color:#555">=</span> <span style="color:#f60">7</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>                ch_out <span style="color:#555">&lt;=</span> ch8_buffer;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h4 id="aes3-transmitter">
    AES3 Transmitter 
    
    <a class="header-link" href="#aes3-transmitter">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Here I can't take much of the credit, I heavily based my work on the following S/PDIF transmitter project:</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://ackspace.nl/wiki/SP/DIF_transmitter_project">https://ackspace.nl/wiki/SP/DIF_transmitter_project</a>
            </div>
        </blockquote>
    

<p>But I adapted it to support the long 384-bit vector for the two channel statuses as well as supporting the user bits and validity bit. Spoiler: this is going to bite me in the ass later!</p>
<h3 id="receiver">
    Receiver 
    
    <a class="header-link" href="#receiver">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Here is the overview of how the receiver works:</p>
<p>
    <figure>
        <a target="_blank" href="diagrams/receiver.drawio.png" >
            <img alt="Ultranet receiver overview" src="/ultranet-adventures-part-1/diagrams/receiver.drawio_hufe6bd35ff790c83910bd38557e9a59b3_499194_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h4 id="aes3-receiver">
    AES3 Receiver 
    
    <a class="header-link" href="#aes3-receiver">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>I can't take credit for this block either, it's almost a copy-paste of the fantastic aes3rx project:</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://opencores.org/projects/aes3rx">https://opencores.org/projects/aes3rx</a>
            </div>
        </blockquote>
    

<p>This block takes a very fast clock plus the aes3 input and spits out the recovered clock signals (bit clock &amp; word clock), the sound data and the block start signal.</p>
<p>It also output an &quot;active&quot; signal that can be used to indicate that a signal is being received.</p>
<h4 id="clocks-1">
    Clocks 
    
    <a class="header-link" href="#clocks-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Just like the transmitter, the <code>Clocks</code> block is responsible for deriving the I2S clock signals based on the AES3 clocks and bsync signal. It takes the 12.288 MHz bit clock and 192 kHz word clock and generates the following clocks:</p>
<ul>
<li>I2S Bit clock running at 3.072 MHz</li>
<li>I2S Word clock running at 48Khz</li>
</ul>
<p>The clock dividers are a bit different from the ones used for the transmitter. First, I have edge detectors for <code>bsync</code>, <code>aes_bclk</code> and <code>aes_lrclk</code>.</p>
<p>Then the I2S bit clock divider is a standard divider with the addition of bsync check so that it resets to the correct state.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">i2s_bit_clock</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(aes_bclk)  <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span>(rising_edge(aes_bclk)) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#09f;font-style:italic">-- Make sure the clock is properly synchronized at the block start</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        <span style="color:#069;font-weight:bold">if</span>(bsync_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            i2s_bclk <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            count_i2s_bclk <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            <span style="color:#09f;font-style:italic">-- Divide the AES3 bit clock to get the I2S bit clock </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            count_i2s_bclk <span style="color:#555">&lt;=</span> count_i2s_bclk <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            <span style="color:#069;font-weight:bold">if</span>(count_i2s_bclk <span style="color:#555">=</span> <span style="color:#f60">1</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>                i2s_bclk <span style="color:#555">&lt;=</span> <span style="color:#069;font-weight:bold">not</span> i2s_bclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                count_i2s_bclk <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>             <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span> <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><p>The word clock is even more complex, instead of synchronizing to only the AES3 word clock and the bsync signal, I also make sure it's properly synchronized to the bit clock:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">i2s_lr_clock</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span>(rising_edge(clk)) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#09f;font-style:italic">-- Make sure the clock is properly synchronized at the block start</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        <span style="color:#069;font-weight:bold">if</span>(bsync <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            i2s_lrclk <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            count_i2s_lrclk <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            <span style="color:#09f;font-style:italic">-- Make sure the word clock is properly synchronized to the bit clock</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            <span style="color:#069;font-weight:bold">if</span>(aes_bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">and</span> aes_lrclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                <span style="color:#09f;font-style:italic">-- Divide the AES3 word clock to get the I2S word clock </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>                count_i2s_lrclk <span style="color:#555">&lt;=</span> count_i2s_lrclk <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                <span style="color:#069;font-weight:bold">if</span>(count_i2s_lrclk <span style="color:#555">=</span> <span style="color:#f60">1</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>                    i2s_lrclk <span style="color:#555">&lt;=</span> <span style="color:#069;font-weight:bold">not</span> i2s_lrclk;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                    count_i2s_lrclk <span style="color:#555">&lt;=</span><span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h4 id="ultranet-deserializer">
    Ultranet Deserializer 
    
    <a class="header-link" href="#ultranet-deserializer">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The Ultranet deserializer block is similar to the I2S deserializer of the transmitter. It is responsible for reading the bits of the serial audio data coming from AES3 receiver block in sync with the different clock signals. It then outputs a 24bit vector containing the audio data, a 3bit vector containing the channel index associated with the data and a &quot;new data&quot; signal that is asserted when the data is ready to be used.</p>
<p>Just like the I2S deserializer, there are two processes, the first one is the one that makes the counters tick and keeps things in sync.
The second one is reading the serial data and shifting it into the buffer when told so by the first process.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">detect_sample</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#09f;font-style:italic">-- Check if we are starting a new block</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        <span style="color:#069;font-weight:bold">if</span> bsync_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            <span style="color:#09f;font-style:italic">-- Yes, reset counters and signals</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            bit_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            channel_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">4</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            <span style="color:#09f;font-style:italic">-- No, check if we are on the rising or falling edge of the word clock</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            <span style="color:#069;font-weight:bold">if</span> lrclk_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                <span style="color:#09f;font-style:italic">-- Yes, reset the bit counter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>                bit_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                <span style="color:#09f;font-style:italic">-- Increment the channel counter up to the defined number of channels then reset to 0</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                <span style="color:#069;font-weight:bold">if</span> channel_counter <span style="color:#555">&lt;</span> (CHANNELS<span style="color:#555">-</span><span style="color:#f60">1</span>) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>                    channel_counter <span style="color:#555">&lt;=</span> channel_counter <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>                <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>                    channel_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>            <span style="color:#09f;font-style:italic">-- Increment the bit counter (up to 32 bits) on a rising edge of the bit clock</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>            <span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                <span style="color:#069;font-weight:bold">if</span> bit_counter <span style="color:#555">&lt;</span> <span style="color:#f60">31</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>                    bit_counter <span style="color:#555">&lt;=</span> bit_counter <span style="color:#555">+</span> <span style="color:#f60">1</span>; <span style="color:#09f;font-style:italic">-- increment bit_counter until bit 31</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>                
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>            <span style="color:#09f;font-style:italic">-- Sample the data on the falling edge of the bit clock</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>            <span style="color:#069;font-weight:bold">if</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>  	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>                <span style="color:#09f;font-style:italic">-- Set the audio data region signal if we are in the correct range [....AAAASSSSSSSSSSSSSSSSSSSSEUCP]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>                <span style="color:#069;font-weight:bold">if</span> bit_counter <span style="color:#555">=</span> <span style="color:#f60">4</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>                    in_sound_data_region <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>                <span style="color:#069;font-weight:bold">elsif</span> bit_counter <span style="color:#555">&gt;=</span> <span style="color:#f60">24</span><span style="color:#555">+</span><span style="color:#f60">4</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>                    in_sound_data_region <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>                
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>            <span style="color:#09f;font-style:italic">-- Assert the new data signal within the unused 4bits at the end of the frame</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>            <span style="color:#069;font-weight:bold">if</span> bit_counter <span style="color:#555">=</span> <span style="color:#f60">30</span> <span style="color:#069;font-weight:bold">and</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>                new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;1&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>            <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>                new_data <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>            <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span><span style="color:#09f;font-style:italic">-- Send the data out</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>sample_out <span style="color:#555">&lt;=</span> sample_data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>channel <span style="color:#555">&lt;=</span> to_unsigned(channel_counter, channel<span style="color:#309">&#39;length</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span><span style="color:#0a8;font-weight:bold">get_data</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>    <span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>        <span style="color:#09f;font-style:italic">-- Receive individual bits for audio-data (24 bits) on the rising edge of the bit clock and only if in the correct region</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>        <span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">and</span> in_sound_data_region <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>            <span style="color:#09f;font-style:italic">-- in AES3/EBU the first bit after preamble is LSB, so we have to shift from the left to the right</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>            sample_data <span style="color:#555">&lt;=</span> sdata <span style="color:#555">&amp;</span> sample_data(sample_data<span style="color:#309">&#39;high</span> <span style="color:#069;font-weight:bold">downto</span> <span style="color:#f60">1</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>        <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span>    <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h4 id="ultranet-demuxer">
    Ultranet demuxer 
    
    <a class="header-link" href="#ultranet-demuxer">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The Ultranet demuxer is probably the simplest block on this whole project. In retrospect, it should probably be integrated into the deserializer. The only thing it does is splitting the audio data into individual vectors according to the index received when the new data signal is asserted:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#069;font-weight:bold">if</span> (rising_edge(clk)) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>	    <span style="color:#09f;font-style:italic">-- Store individual channels to output-vectors on the rising edge of new data</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>		<span style="color:#069;font-weight:bold">if</span> new_data_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>			<span style="color:#069;font-weight:bold">if</span> channel <span style="color:#555">=</span> <span style="color:#f60">0</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>				ch1_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>				ch2_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">2</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>				ch3_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">3</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>				ch4_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">4</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>				ch5_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">5</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>				ch6_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">6</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>				ch7_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>			<span style="color:#069;font-weight:bold">elsif</span> channel <span style="color:#555">=</span> <span style="color:#f60">7</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>				ch8_out <span style="color:#555">&lt;=</span> sample_in;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>			<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>		<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>	<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;
</span></span></code></pre></div><h4 id="i2s-transmitter">
    I2S Transmitter 
    
    <a class="header-link" href="#i2s-transmitter">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Thanks to the move from I2S mode to left-justified mode on the DACs the implementation of the transmitter is basic.
As always, there are some edge detectors, followed by the two main processes.</p>
<p>The first one is keeping the bit counter in sync in relation to the bit clock and reset signal. It also handles loading the sample buffer on the last bit.
The second process simply output the audio data to the correct channel.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0a8;font-weight:bold">detect_sample</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>		<span style="color:#069;font-weight:bold">if</span> reset_n <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>			bit_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>		<span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            <span style="color:#09f;font-style:italic">-- If the left/right channel changes, reset the output bit counter</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>			<span style="color:#069;font-weight:bold">if</span> lrclk_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>				bit_counter <span style="color:#555">&lt;=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>			<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            <span style="color:#09f;font-style:italic">-- Increment the bit counter a positive edge of the bit clock</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>			<span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>				bit_counter <span style="color:#555">&lt;=</span> bit_counter <span style="color:#555">+</span> <span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>			<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>            <span style="color:#09f;font-style:italic">-- Update the sample buffers on the last positive bit clock edge </span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>            <span style="color:#09f;font-style:italic">-- TODO: Shouldn&#39;t this be on the negative edge of LRCLK?</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>			<span style="color:#069;font-weight:bold">if</span> bclk_pos_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>  	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>				<span style="color:#069;font-weight:bold">if</span> bit_counter <span style="color:#555">=</span> DATA_WIDTH<span style="color:#555">-</span><span style="color:#f60">1</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>                    sample_ch_1_l_buf <span style="color:#555">&lt;=</span> sample_ch_1_l;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>                    sample_ch_1_r_buf <span style="color:#555">&lt;=</span> sample_ch_1_r;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>                    sample_ch_2_l_buf <span style="color:#555">&lt;=</span> sample_ch_2_l;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>                    sample_ch_2_r_buf <span style="color:#555">&lt;=</span> sample_ch_2_r;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                    sample_ch_3_l_buf <span style="color:#555">&lt;=</span> sample_ch_3_l;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>                    sample_ch_3_r_buf <span style="color:#555">&lt;=</span> sample_ch_3_r;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>                    sample_ch_4_l_buf <span style="color:#555">&lt;=</span> sample_ch_4_l;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>                    sample_ch_4_r_buf <span style="color:#555">&lt;=</span> sample_ch_4_r;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>				<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>			<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>		<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>	<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span><span style="color:#0a8;font-weight:bold">send_data</span><span style="color:#555">:</span> <span style="color:#069;font-weight:bold">process</span>(clk) <span style="color:#069;font-weight:bold">begin</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>	<span style="color:#069;font-weight:bold">if</span> rising_edge(clk) <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>		<span style="color:#069;font-weight:bold">if</span> reset_n <span style="color:#555">=</span> <span style="color:#c30">&#39;0&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>            <span style="color:#09f;font-style:italic">-- Reset asserted, send zeros to the output</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>			sdout_1 <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>			sdout_2 <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>			sdout_3 <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>			sdout_4 <span style="color:#555">&lt;=</span> <span style="color:#c30">&#39;0&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>		<span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>            <span style="color:#09f;font-style:italic">-- Normal state, output the proper bit (left or right) for each channel on the negative bit clock edge</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>			<span style="color:#069;font-weight:bold">if</span> bclk_neg_edge <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>  	
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>                <span style="color:#069;font-weight:bold">if</span> lrclk <span style="color:#555">=</span> <span style="color:#c30">&#39;1&#39;</span> <span style="color:#069;font-weight:bold">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>                    sdout_1 <span style="color:#555">&lt;=</span> sample_ch_1_l_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>                    sdout_2 <span style="color:#555">&lt;=</span> sample_ch_2_l_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>                    sdout_3 <span style="color:#555">&lt;=</span> sample_ch_3_l_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>                    sdout_4 <span style="color:#555">&lt;=</span> sample_ch_4_l_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>                <span style="color:#069;font-weight:bold">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>                    sdout_1 <span style="color:#555">&lt;=</span> sample_ch_1_r_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>                    sdout_2 <span style="color:#555">&lt;=</span> sample_ch_2_r_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span>                    sdout_3 <span style="color:#555">&lt;=</span> sample_ch_3_r_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>                    sdout_4 <span style="color:#555">&lt;=</span> sample_ch_4_r_buf(DATA_WIDTH <span style="color:#555">-</span> <span style="color:#f60">1</span> <span style="color:#555">-</span> bit_counter);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>                <span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>			<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>		<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>	<span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">if</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span><span style="color:#069;font-weight:bold">end</span> <span style="color:#069;font-weight:bold">process</span>;    
</span></span></code></pre></div><h3 id="its-working">
    It's working? 
    
    <a class="header-link" href="#its-working">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Well, after a lot of tuning, the answer is YES!🎉 And honestly, it's working pretty well considering that this is my first time working with FPGAs. Here's a logic analyzer capture I took right after I got everything up and running for the first time 🏆:</p>
<p>
    <figure>
        <a target="_blank" href="images/DSView_2025-03-02_19-12-29_77093cbd-9a9c-4458-b470-dad0f42aa252.png" >
            <img alt="Logic analyzer capture of the first working test" src="/ultranet-adventures-part-1/images/_hu80be228febe14ae2ca02bb050268af2f_160035_074e04692c4cfc98ad998825bec242de.webp" />
        </a>
        <figcaption>Logic analyzer capture of the first working test</figcaption>
    </figure>

</p>
<p>I'm much happier with the implementation of the receiver, it's much more stable/polished than the transmitter. The biggest thing by far is that the <code>reset</code> signal is actually used and everything re-sync correctly after it has been asserted.</p>
<p>I think that, the only reason the transmitter seems to work at all is that the clocks are never actually stopped during operation 😅.</p>
<p>Still, it’s more than good enough for a first prototype 👍.</p>
<h2 id="testing-on-real-hardware">
    Testing on real hardware 
    
    <a class="header-link" href="#testing-on-real-hardware">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So let's have fun with some real hardware that supports Ultranet. I managed to borrow these devices:</p>
<ul>
<li>The <a target="_blank" href="https://www.midasconsoles.com/product.html?modelCode=0606-ACJ">Midas DL-16</a> which is a <code>16 Input, 8 Output Stage Box with 16 Midas Microphone Preamplifiers, ULTRANET and ADAT Interfaces</code>.<br>I can use it in standalone mode as an overkill A/D converter to convert the 16 analog inputs to Ultranet.</li>
<li>The <a target="_blank" href="https://www.turbosound.com/product.html?modelCode=0315-ABC">Turbosound TFX122M-AN</a> which is a <code>Coaxial 1100W 2-Way 12&quot; Stage Monitor with Klark Teknik DSP Technology and ULTRANET</code>.<br>Unfortunately, it's the only piece of hardware with an Ultranet input I had access to. It has the downside of being a loudspeaker so bit-shift mistakes at 2am are quite annoying. But at the same time, the configuration app proved unexpectedly useful for debugging.</li>
</ul>
<h3 id="transmitter-1">
    Transmitter 
    
    <a class="header-link" href="#transmitter-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I started by verifying the transmitter. I was feeling pretty confident since everything had worked on my devboard so far. So, I plugged the RJ-45 cable into the speaker, expecting things to work. But then, disaster struck 🌪️. I couldn't even select the Ultranet input from the speaker's built-in controls.</p>
<p>F---, that means that either my implementation is not working at all or that I messed-up something 😕. I decided to troubleshoot by downloading the Turbosound Edit app and connecting to the speaker. Unexpectedly, I was able to force the input to be an Ultranet input but was then immediately prompted by this error message:</p>
<p>
    <figure>
        <a target="_blank" href="images/TURBOSOUND_Edit_2025-04-06_20-46-33_e1f5a3e2-dbb3-4682-a630-ee6c3f0b8c04.png" >
            <img alt="Turbosound Edit - Forced Ultranet error message" src="/ultranet-adventures-part-1/images/TURBOSOUND_Edit_2025-04-06_20-46-33_e1f5a3e2-dbb3-4682-a630-ee6c3f0b8c04.png" />
        </a>
        <figcaption>Turbosound Edit - Forced Ultranet error message</figcaption>
    </figure>

</p>
<p>To my surprise 🤯, even though the error appeared, the speaker did seem to switch to the Ultranet input. I started hearing something that could only be described as audio thrown into a blender, chopped up, and distorted (Fun fact this first test was at 3am, I wasn't the favorite person in the morning!).</p>
<p>This told me that my implementation wasn’t completely broken. It was close, but just not quite there yet.</p>
<p>After much troubleshooting 🛠️, I figured out that the Ultranet inverts the validity bit. Once I applied this fix, I was able to get rid of the <code>Check ULTRANET!</code> error message. This discovery not only unlocked the built-in controls but also the VU-meter on the Turbosound Edit app:</p>
<p>
    <figure>
        <a target="_blank" href="images/TURBOSOUND_Edit_2025-03-16_22-20-29_69396e41-7bf7-4066-8aa6-1f594bbc2d35.png" >
            <img alt="Turbosound Edit - Sound on wrong channel" src="/ultranet-adventures-part-1/images/TURBOSOUND_Edit_2025-03-16_22-20-29_69396e41-7bf7-4066-8aa6-1f594bbc2d35.png" />
        </a>
        <figcaption>Turbosound Edit - Sound on wrong channel</figcaption>
    </figure>

</p>
<p>This was interesting because I only sent an audio signal on channel 1, not the third one 🧐. During testing, I got multiple variants of this, for example, sending audio on channel 2 resulted in audio on channel 2, 4, 6 and 8.</p>
<p>No matter what adjustments I made, the audio was always barely recognizable, and it sounded like it was skipping samples.</p>
<p>I also went down's the rabbit hole of updating the speaker because I noticed that the changelog included something about Ultranet compatibility. Unfortunately, this didn't change anything to my situation.</p>
<p>At that point, I decided to take a step back from the transmitter and turn my attention to the receiver, maybe I'll have better luck 🍀.</p>
<h3 id="receiver-1">
    Receiver 
    
    <a class="header-link" href="#receiver-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>So, the receiver… well, how do I put this? It was even worse than I expected 😢. I couldn't get anything stable or consistent. All I could capture were small chunks of audio, with the aes3rx module showing an &quot;active&quot; status only intermittently. Definitely not the kind of reliable performance I was aiming for.</p>
<p>At this point, I decided to go back and re-watch the video that originally inspired this whole project 📺. While watching Christian troubleshoot similar issues, I noticed that he had solved his problems by increasing the clock speed going into the aes3rx module, pushing it up to 200 MHz 💨. This immediately stood out to me as a potential fix for my situation.</p>
<p><em>Note: At this point I was still deriving the &quot;main clock&quot; from the 27 MHz crystal provided by the Tang Nano 9k. I was also only providing a ~100MHz clock to the aes3rx module.</em></p>
<p>So, I went ahead and tested this. I decided to increase the PLL factors of the main clock to push it closer to that 200 MHz target. Unfortunately, this is where my lack of experience with FPGAs really showed. No matter what I tried, I couldn't make it work properly. The best I could manage was around 150 MHz, and anything beyond that would cause the module to brick 🧱.</p>
<p>But after much trial and error, I stumbled upon a &quot;solution&quot;. Instead of using the 27 MHz crystal, I switched to the 24.576 MHz clock from the PLL1707. I fed that into the PLL, adjusting the factors until I could reach a 245.76 MHz &quot;main clock&quot;. And just like that… I was able to decode the data.</p>
<p>While this was a breakthrough, I’m not entirely happy with the outcome 😕. For one, it means that 44.1 kHz Ultranet support is now off the table. Moreover, the whole project now relies heavily on a &quot;patch&quot; that I don’t fully understand, something I’ll definitely need to revisit for part 2 🤔.</p>
<p>But hey, at least there is audio coming out of the damn thing 🎉 which means I now needed to fix the transmitter.</p>
<h3 id="transmitter-again">
    Transmitter again 
    
    <a class="header-link" href="#transmitter-again">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>With this renewed boost in confidence 🚀, I was finally ready to tackle the transmitter once more. Knowing that the receiver was working properly, and that the transmitter had worked with my receiver before, meant that a significant portion of the transmitter's implementation was already functioning as expected. The hard part seemed to be behind me.</p>
<p>I quickly ruled out a few possibilities: the preambles were fine, the audio data was correct, and the validity bit was being set properly. That narrowed it down to a few suspects: the channel status bits, the user bits and the parity calculation.</p>
<p>Determined to get to the bottom of this, I painstakingly recorded the Ultranet stream coming from the DL-16 under various input combinations 💾. I spent a few hours capturing 500ms slices of data at 200 MHz, trying to build a database in which I could search for any anomalies that could give me a clue.</p>
<p>
    <figure>
        <a target="_blank" href="images/explorer_2025-04-06_21-34-19_d84b34fe-8cc1-4999-a9ea-0d1c580dcd26.png" >
            <img alt="500ms of Ultranet captures at 200Mhz from the DL16" src="/ultranet-adventures-part-1/images/explorer_2025-04-06_21-34-19_d84b34fe-8cc1-4999-a9ea-0d1c580dcd26.png" />
        </a>
        <figcaption>500ms of Ultranet captures at 200Mhz from the DL16</figcaption>
    </figure>

</p>
<p>After writing a script to analyze all of this 📜, the conclusion was pretty simple:</p>
<p>The parity was untouched (duh, or it wouldn't work with off-the-shelf chips 🤦‍♂️), the user bits were unused (always 0) and the channel status bit repeated the following pattern:</p>
<pre style="word-break: break-all; white-space: pre-wrap;">
000000000000000000000000000000000000000000000000000000000000000011000000111100110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
</pre>
<p>Interpreted like the 2 AES3 subframes but with 8 subframes it would provide this information:</p>
<ul>
    <li style="font-size: unset">Subframe 1: <code>00000000 11000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 2: <code>00000000 11000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 3: <code>00000000 01000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 4: <code>00000000 01000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 5: <code>00000000 00000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 6: <code>00000000 00000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 7: <code>00000000 01000000 00000000 00000000 00000000 00000000</code></li>
    <li style="font-size: unset">Subframe 8: <code>00000000 01000000 00000000 00000000 00000000 00000000</code></li>
</ul>
<p>While writing this article, now that I look at it, byte 2 does look like a channel offset, but who knows if I'm reading that correctly 🤷‍♂️, that's for part 2 of this project.</p>
<p>Coming back to my issue, even after using this bit sequence in the aes3 transmitter (and verifying that the correct bits are indeed being sent), it didn't change much. I did get better sound somehow 🤬🤯.</p>
<h4 id="oh-f----that-was-the-issue">
    Oh, f---, that was the issue! 
    
    <a class="header-link" href="#oh-f----that-was-the-issue">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>At some point, I decided I’d had enough of listening to garbled audio or staring at endless walls 🗑️ and went all-in: I dedicated a full day to analyzing logic analyzer traces instead. And sure enough, that’s when I found it.</p>
<p>Looking closely at the preambles sent by the DL16, I noticed something peculiar. They <strong>always</strong> started with a 1. The typical pattern looked like this: <code>1110010</code>. Not once did I see the alternate pattern that starts with a 0, like <code>0001101</code>.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl-16-pramble.png" >
            <img alt="" src="/ultranet-adventures-part-1/images/dl-16-pramble.png" />
        </a>
        
    </figure>

</p>
<p>I then repeated the process for my implementation and bingo. That’s when I saw the problem: my transmitter was happily alternating between both versions of the preamble.</p>
<p>
    <figure>
        <a target="_blank" href="images/devboard-preambles.png" >
            <img alt="" src="/ultranet-adventures-part-1/images/devboard-preambles.png" />
        </a>
        
    </figure>

</p>
<p>This immediately tickled my brain, why would the signal from Behringer only use one form of the preamble?</p>
<p>That’s when I remembered a modification I made earlier while adding support for user bits in my S/PDIF transmitter module 📝. I knew I'd need to include those in the parity calculation, so I updated the logic like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vhdl" data-lang="vhdl"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>parity <span style="color:#555">&lt;=</span> data_in_buffer(<span style="color:#f60">23</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">22</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">21</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">20</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">19</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">18</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">17</span>)  <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">16</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">15</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">14</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">13</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">12</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">11</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">10</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">9</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">8</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">7</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">6</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">5</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">4</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">3</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">2</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">1</span>) <span style="color:#069;font-weight:bold">xor</span> data_in_buffer(<span style="color:#f60">0</span>) <span style="color:#069;font-weight:bold">xor</span> user_status_shift(<span style="color:#f60">23</span>) <span style="color:#069;font-weight:bold">xor</span> channel_status_shift(<span style="color:#f60">23</span>);
</span></span></code></pre></div><p>But remember how I mentioned earlier that I had extended the user and channel status vectors to 384 bits to match the full length 📏? <br>Yeah. Well, the parity calculation? Still hardcoded to pull from bit 23.</p>
<p>As soon as I fixed the logic to XOR the correct final bits of the expanded vectors, bam. Everything lined up. It just worked 🔥. I also went ahead an added the missing validity bit in the calculation. The reason I got better sound when I added the channel status bits is probably because the parity calculation was correct more often for some reason 😅.</p>
<p>It would seem that in every project there is an issue where I sink countless hours of debugging just to find out it's one of the stupidest mistakes ever. But hey, that's life … 🤡</p>
<h3 id="its-working-encore">
    It's working (encore)? 
    
    <a class="header-link" href="#its-working-encore">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Yes! This time, it’s really working! 🎉</p>
<p>Well almost there is something really strange happening: the channel order is correct, meaning they are in sequence, but the channel offset is all kinds of messed up. For example, if I send audio into channels 1-2 of the DL-16, it always ends up on channels 5-6 on my devboard. Even weirder: when using the &quot;Through&quot; port of the TFX speaker, the channel offset becomes completely random 🎲. Every so often it’s 1-2, other times 3-4, etc.</p>
<p>The odd thing is, when I plug the DL-16 into the TFX and look at the VU meters in the Turbosound Edit app, it indicates that the audio first gets received on the wrong channels, but then automatically corrects itself to the right ones (Probably after the channel status has been received completely). This is the fishy behavior I mentioned in the research section of this article 🐟.</p>
<p>
    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/DDXtz1vR-gg"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<br></p>
<p>But hey, let’s not get bogged down in the details. <strong>I GOT ULTRANET WORKING!!!🥳</strong> And, more importantly, this weird offset issue doesn’t seem to affect devboard-to-devboard communication, which is somewhat of a relief 😌.</p>
<p>After around two and a half months of intense work, countless hours of testing, and plenty of moments wondering if I was losing my mind, I finally have a fully functional transmitter and receiver implementation of Ultranet.</p>
<p>This was no small feat. Ultranet is a proprietary protocol, it is based on AES3, but with its quirks and implementation challenges. Without access to official documentation, a good portion of the process involved reverse-engineering, experimenting, and a lot of trial and error. But ultimately, it paid off.</p>
<p>Both the transmitter and receiver are now reliably exchanging data in sync, and the system is behaving almost as expected 🥳.</p>
<p>
    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/_Ll4hrwM3Vc"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<br></p>
<h2 id="whats-next">
    What's next 
    
    <a class="header-link" href="#whats-next">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So… what’s next? 🤔 If you’ve been reading closely, you probably noticed me hinting at a part 2 more than a few times throughout this article. For once, I’m actually going to split both the development process and the write-up into two parts. This first &quot;prototype&quot; phase is already a beast on its own, and I want to give the next phase of the project the attention it deserves.</p>
<p>Part 2 will pick up where this leaves off. Here's a brief look at what that will include:</p>
<ul>
<li>🧮 (Hopefully) Tackling the channel indexing issue: <strong>This is the one big unknown about Ultranet that is still left</strong>, and I want to figure out the damn thing.</li>
<li>🔌 Schematic: I want to revisit the audio side of the design, particularly on adding proper balanced inputs and outputs.</li>
<li>🧩 Final PCB layout(s): This first version was mostly a proof of concept. Part 2 will focus on finalizing the board design which will probably be multiple interconnected ones.</li>
<li>📦 1U/2U Racked case: Time to move from bare PCB on the bench to something that actually looks and feels like a finished product.</li>
</ul>
<p>Now, just to set expectations: this second part is still a long way off. I usually don’t start writing about a project until it’s at least 90-95% complete, and as of right now… well, the design phase hasn’t even begun. It is coming because I have a (very loose) deadline, but might be a while before you see the next article about this project 📅.</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Throughout this project, I questioned, more than once whether sticking with an FPGA implementation was the right call 🤔. There were definitely moments where the idea of switching to the AK4114 felt tempting. It would’ve simplified many things and saved me from a few late nights staring at timing diagrams wondering what broke this time. But the AK4114 is officially end-of-life, not to mention pretty expensive for what it does so that option was quickly shelved 🗄️.</p>
<p>There are other chips on the market from Texas Instruments and others, that could’ve filled the gap. But let’s be honest: where’s the fun in taking the easy route when you could be fighting with VHDL and constraints files instead? 😭.</p>
<p>I don't think I would go with the FPGA route if this was a work project, but as this is a side project that will be used in a non-critical part of my infrastructure, I achieved my primary goal with this massive project: learning 👨🏻‍🎓. Diving into the deep end with the FPGA kept the challenge alive, and it ended up being a fantastic learning experience</p>
<p>By sticking to the original goals, I set out at the beginning, I had to understand how things and why. And as frustrating as that was at times, it paid off. My understanding of FPGAs, and real-time audio protocols has improved quite a bit. Honestly, there’s no better way to learn than by getting your hands dirty, messing around with stuff, breaking it 🪓, and then figuring out how to fix it. It’s chaotic 💥, but it works.</p>
<p>Looking back, I'm incredibly happy with what I’ve built so far 🤩. This is only Part 1 of the journey, but even at this stage, it's a solid milestone 🚩.</p>
<p>If you’ve made it this far, thanks for reading! I hope Part 2 ends up being even better, and who knows, maybe next time I’ll even sleep. Maybe.</p>

 ]]></content:encoded></item><item><title>Building an open-source bi-directional SDI to Fiber converter</title><description> &lt;p>Creating a DIY open-source and budget-friendly bidirectional 3G-SDI to fiber converter using readily available components and determination.&lt;/p></description><link>https://blog.thestaticturtle.fr/diy-opensource-bidirectional-sdi-to-fiber-converter/</link><guid>https://blog.thestaticturtle.fr/diy-opensource-bidirectional-sdi-to-fiber-converter/</guid><category> Electronics</category><category> Opensource</category><category> Diy</category><category> Video</category><category> Concerts</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 02 Dec 2024 09:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/diy-opensource-bidirectional-sdi-to-fiber-converter/images/cover_hua62d3f79be9d512036ad26894d69e1c3_2243945_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/diy-opensource-bidirectional-sdi-to-fiber-converter/images/cover_hua62d3f79be9d512036ad26894d69e1c3_2243945_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Building an open-source bi-directional SDI to Fiber converter</h1>
<span class="subtitle"><p>Creating a DIY open-source and budget-friendly bidirectional 3G-SDI to fiber converter using readily available components and determination.</p></span>
<br>

    <img class="" src='/diy-opensource-bidirectional-sdi-to-fiber-converter/images/cover_hua62d3f79be9d512036ad26894d69e1c3_2243945_1350x900_fit_q80_box.jpg' alt="Building an open-source bi-directional SDI to Fiber converter"/>

<hr>

<blockquote class="warning">
    
    
        <p class="warning-text">
This is a stupidly long article, it details the different phases thoroughly, you'll need probably some time to really read it
</p>
    
</blockquote>
<p>Every once in a while, I have the pleasure of working with my local choir. Every few years, they perform on stage, featuring around 3 hours of music 🎶. In the past, my involvement was mostly behind the scenes👻, helping wherever I could. However, last year, I decided to take on a more ambitious role and conduct an experiment that would elevate the entire performance.</p>
<p>I took every piece of video equipment I could get my hands on and set out to record the shows and provide live Image Magnification (IMAG) of the band 🎥. This would be a significant step up from previous years, where recording had been outsourced with mixed results. The idea was to bring a new level of engagement for both the audience and the performers, while also creating a high-quality record of the event 🎞.</p>
<p>The rest of the tech staff later admitted that they were, at first, skeptical of this 🤨. But it turned out to be a resounding success. The live IMAG was enthusiastically received by both the singers and the audience, adding a dynamic visual element that enhanced the overall experience 🥳. It allowed those seated further back to see the work of the musicians 🥁. As for the recording, the quality far exceeded my expectations, surpassing in many ways what had been achieved in previous years 🤩.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_7224.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_7224.jpg" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_7254.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_7254.jpg" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_7256.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_7256_huf2b760eda6d7122f00a943c1eaccd634_183958_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="why">
    Why 
    
    <a class="header-link" href="#why">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The last show was wonderful, but I always want to improve things. Something that's been bugging me in my video setup is that it was a mess. I mean, I had HDMI cables here, SDI there, some RTSP streams, and even NDI feeds. Talk about a tech salad 🥗!</p>
<p>Don't get me wrong, it worked… <em>mostly</em>, and that's what happens when your budget is almost none. But I had to run custom scripts before the show 📜 and more than once the IMAG showed the blank Elgato logo or a freeze-frame ⬛. So, I kept thinking, &quot;There's got to be a better way to do this.&quot;</p>
<p>As I mentioned, the issue had always been the budget 💰, I was at the time an apprentice in a different field with no contacts or knowledge in the AV production world. Video isn't exactly the cheapest part of a live production. The technology evolves rapidly, which means the quality is always increasing.</p>
<p>So for the next show, I want to do one thing: simplify the heck out of my video system, specifically transport 🔌.</p>
<p>I started digging into different options and, there are a few contenders:</p>
<ul>
<li><strong>NDI:</strong> This system is pretty slick, but there is a catch: you either need fancy cameras that speak NDI right out of the box, or you have to shell out for converters that will set me back at least 300 EUR a pop 🙁. Not to mention that if you want to get low latency, you need special mixers.</li>
<li><strong>RTSP:</strong> Now, this one's tempting: It's wonderful and very popular, but it's got a bit of a lag issue, and you still need converter boxes. Although they are a bit cheaper.</li>
<li><strong>HDMI:</strong> It's pretty reliable but length is a severe issue 🔭 unless you use active cables.</li>
<li><strong>SDI:</strong> Now we're talking! This is like the Goldilocks of video options for me. It's not dirt cheap, but compared to the others, it's a steal. You can find affordable converters to and from HDMI and it's used all over the place in pro setups.</li>
</ul>
<p>But wait, it gets better! SDI got a neat trick up its sleeve - you can easily convert it to fiber ⚪. Like effortlessly convert it!</p>
<p>My idea is the following: an MPO-12 breakout (fancy connector with 12 fibers) and an armored MPO-12 cable. Throw them in a DIY stage box, and boom! We're in business: one cable, multiple cameras.
This setup would be like the Swiss Army knife 🔪 of video transport. I'm thinking of something like 2 fibers for 10 Gb Ethernet 🌐, at least 8 for video feeds 📹, and 2 spares that I'll probably use for audio in the future.</p>
<p>However, the cost of fiber converters can be a hurdle, almost as much as IP converters (NDI/RTSP). For example, Blackmagic offers a 12G SDI to fiber converter priced at around 155 EUR 💸:</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://www.blackmagicdesign.com/fr/products/miniconverters/techspecs/W-CONM-29">https://www.blackmagicdesign.com/fr/products/miniconverters/techspecs/W-CONM-29</a>
            </div>
        </blockquote>
    

<p>While this is a reliable and professional option, the price tag is a bit hefty for a project that's meant to be low-cost and accessible. I don't really need 8k video, 1080p60 is more than fine for IMAG, so 3G-SDI will do.</p>
<p>Unfortunately, I couldn't find a cheaper alternative, even for lower data rates. I was on the lookout for more budget-friendly alternatives, and purely by chance, I stumbled upon this post on X by twi_kingyo.</p>

    
        

        
            






    
    



    
        <blockquote class="embed embed-twitter">
            
                
                    
                    
                    
                    
                        <a href="https://x.com/twi_kingyo/status/1446102240633049094" target="_blank" class="embed-image-container">
                            <div class='embed-image-background' style='background-image: url(/images/25bfa2461e913160b2739ab73713da58_hu1a43a3fcbf9ca3298b5aaf48184949b4_0_0x720_resize_q90_h2_box.webp);'></div>
                            <div class="embed-image-image embed-twitter-mediatype-photo">
                                
                                <img src='/images/25bfa2461e913160b2739ab73713da58_hu1a43a3fcbf9ca3298b5aaf48184949b4_0_0x720_resize_q90_h2_box.webp'>
                            </div>
                        </a>
                    
                
            

            <div class="embed-content">
                <a class="embed-title" target="_blank" href="https://x.com/twi_kingyo/status/1446102240633049094">金魚</a>
                
                    <p>適当なSFPモジュールで3G-SDI信号伝送できちゃって草　　数10kmを光ファイバ伝送できちゃうな </p>
                
                
            </div>
        </blockquote>
    

        
    

<p>The post was a game-changer for me. From the picture, it certainly looks like SDI-HDMI converters plugged into a test board with a fiber cable in the middle. Very crude, DIY approach to 3G-SDI to fiber conversion, but that looked both affordable and in line with what I had been envisioning.</p>
<p>As I read a bit more, I immediately thought that this is precisely what I want to do</p>
<h2 id="research">
    Research 
    
    <a class="header-link" href="#research">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I started this journey by researching a ton on the two major components of this project, namely SDI and SFPs.</p>
<h3 id="sfp--sfp">
    SFP / SFP+ 
    
    <a class="header-link" href="#sfp--sfp">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>SFP (Small Form-factor Pluggable) and SFP+ (enhanced SFP) modules are compact, hot-swappable transceivers used in networking to transmit and receive data over optical or copper connections. They are widely used in switches, routers, and other networking equipment.</p>
<p>Their key features include</p>
<ul>
<li>Form Factor: Small size allows for high port density.</li>
<li>Hot-swappable: Can be replaced without turning off the device.</li>
<li>Versatility: Supports various protocols (1GbE / 10GbE / Video).</li>
</ul>
<p>See this Wikipedia page about them for more information:

    
        

        
            








    


<blockquote class="embed embed-en_wikipedia_org">
    
        
            <a target="_blank" href="https://en.wikipedia.org/wiki/Small_Form-factor_Pluggable" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/16dbedb42f5823cd84c3467485ccf3a2_hu8f8417bd38a8e553e2e7c1700494b88d_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/16dbedb42f5823cd84c3467485ccf3a2_hu8f8417bd38a8e553e2e7c1700494b88d_0_0x720_resize_q90_h2_box.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://en.wikipedia.org/wiki/Small_Form-factor_Pluggable">Small Form-factor Pluggable - Wikipedia</a>
        
        
    </div>
</blockquote>
        
    
</p>
<p>SFP modules take very few signals:</p>
<table>
<thead>
<tr>
<th>Description</th>
<th style="text-align:right">Pin</th>
<th style="text-align:center">Pin No</th>
<th style="text-align:center">Pin No</th>
<th style="text-align:left">Pin</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Transmitter ground</td>
<td style="text-align:right">VeeT</td>
<td style="text-align:center">20</td>
<td style="text-align:center">1</td>
<td style="text-align:left">VeeT</td>
<td>Transmitter ground</td>
</tr>
<tr>
<td>Transmit differential pair (Neg)</td>
<td style="text-align:right">TD-</td>
<td style="text-align:center">19</td>
<td style="text-align:center">2</td>
<td style="text-align:left">TxFault</td>
<td>Transmitter fault indication</td>
</tr>
<tr>
<td>Transmit differential pair (Pos)</td>
<td style="text-align:right">TD+</td>
<td style="text-align:center">18</td>
<td style="text-align:center">3</td>
<td style="text-align:left">TxDisable</td>
<td>Optical output disabled when high</td>
</tr>
<tr>
<td>Transmitter ground</td>
<td style="text-align:right">VeeT</td>
<td style="text-align:center">17</td>
<td style="text-align:center">4</td>
<td style="text-align:left">MOD-DEF(0)</td>
<td>2-wire serial interface data line</td>
</tr>
<tr>
<td>Transmitter power (3.3 V, max. 300 mA)</td>
<td style="text-align:right">VccT</td>
<td style="text-align:center">16</td>
<td style="text-align:center">5</td>
<td style="text-align:left">MOD-DEF(1)</td>
<td>2-wire serial interface clock</td>
</tr>
<tr>
<td>Receiver power (3.3 V, max. 300 mA)</td>
<td style="text-align:right">VccR</td>
<td style="text-align:center">15</td>
<td style="text-align:center">6</td>
<td style="text-align:left">MOD-DEF(2)</td>
<td>Module absent, GND indicates module presence</td>
</tr>
<tr>
<td>Receiver ground</td>
<td style="text-align:right">VeeR</td>
<td style="text-align:center">14</td>
<td style="text-align:center">7</td>
<td style="text-align:left">RS0</td>
<td>Rate select 0</td>
</tr>
<tr>
<td>Receive differential pair (Pos)</td>
<td style="text-align:right">RD+</td>
<td style="text-align:center">13</td>
<td style="text-align:center">8</td>
<td style="text-align:left">LOS</td>
<td>Receiver loss of signal indication</td>
</tr>
<tr>
<td>Receive differential pair (Neg)</td>
<td style="text-align:right">RD-</td>
<td style="text-align:center">12</td>
<td style="text-align:center">9</td>
<td style="text-align:left">RS1</td>
<td>Rate select 1</td>
</tr>
<tr>
<td>Receiver ground</td>
<td style="text-align:right">VeeR</td>
<td style="text-align:center">11</td>
<td style="text-align:center">10</td>
<td style="text-align:left">VeeR</td>
<td>RX Ground</td>
</tr>
</tbody>
</table>
<p>Most of them don't need to be dynamically adjusted, and some can be ignored outright. As long as the module gets power, has the correct rate and has its transmitter is enabled, it should work fine.</p>
<p>What's even better is that basic modules (short distance, multimode ones) are relatively &quot;passive&quot;. Some include a reclocker that resyncs the data, but not always.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024_09_17_16-56-28_RRY2TFfSrr.png" >
            <img alt="Block diagram of an SFP/SFP&#43;" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024_09_17_16-56-28_RRY2TFfSrr.png" />
        </a>
        
    </figure>

</p>
<p>This means that, as long as it's a differential signal, you can feed just about anything to the module</p>

    
        

        
            









<blockquote class="embed embed-reddit">
    
        
            <a target="_blank" href="https://www.reddit.com/r/techsupportmacgyver/comments/hwptl0/uart_over_sfp_fiber_optic_finally_working/" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/89db34728090425fea9f30396587fde9_hud78ac48b01b3413aa10f9ddcffaaeba0_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/89db34728090425fea9f30396587fde9_hud78ac48b01b3413aa10f9ddcffaaeba0_0_0x720_resize_q90_h2_box.webp' alt='From the techsupportmacgyver community on Reddit: &amp;quot;UART over sfp fiber optic finally working&amp;quot;'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://www.reddit.com/r/techsupportmacgyver/comments/hwptl0/uart_over_sfp_fiber_optic_finally_working/">From the techsupportmacgyver community on Reddit: UART over sfp fiber optic finally working</a>
        
        
            <p>Explore this post and more from the techsupportmacgyver community</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-hackaday">
    
        
            <a target="_blank" href="https://hackaday.com/2021/02/13/experiment-with-sfp-modules-with-this-handy-breakout/" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/d1cec26ca19eb6041abc05bf9e07f35e_hudc7ed3c89b39eb36867ca899fd173a89_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/d1cec26ca19eb6041abc05bf9e07f35e_hudc7ed3c89b39eb36867ca899fd173a89_0_0x720_resize_q90_h2_box.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://hackaday.com/2021/02/13/experiment-with-sfp-modules-with-this-handy-breakout/">Experiment With SFP Modules With This Handy Breakout</a>
        
        
            <p>While most home networking hardware comes with network ports baked in from the factory, industrial grade gear is typically more versatile. Using standards like Small Form-factor Pluggable, or SFP, …</p>
        
    </div>
</blockquote>
        
    

<p>And, as long as they support it, they can be relatively easily managed using the <a target="_blank" href="https://cdn.hackaday.io/files/21599924091616/AN_2030_DDMI_for_SFP_Rev_E2.pdf">DDM (Digital Diagnostics Monitoring)</a> protocol</p>

    
        

        
            








    


<blockquote class="embed embed-hackaday_io">
    
        
            <a target="_blank" href="https://hackaday.io/project/21599-optical-power-meter-with-sfp-and-ddm-protocol" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/59992c3b8b6b276066597a8d0794d33a_hu5810c85ca78de07836b9b44ce8f4d613_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/59992c3b8b6b276066597a8d0794d33a_hu5810c85ca78de07836b9b44ce8f4d613_0_0x720_resize_q90_h2_box.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://hackaday.io/project/21599-optical-power-meter-with-sfp-and-ddm-protocol">Optical Power Meter (with SFP and DDM protocol)</a>
        
        
            <p>To build DIY optical power meter with standard SFP module and Arduino - Can measure optical power in dbm and watt - Can Enable/Disable TX power output (laser source) - Can debug via UART And Arduino Library - a lib for SFP/DDM interfacing (not only optical sfp transceiver - to interface and interpret DDM (Digital Diagnostics Monitoring ) protocol (which used in most optical fiber communication) scheme and format - to debug, monitor, detect alarm and control SFP via DDM - to extend the capability of handling data TX/RX interface (Arduino data over optical fiber)</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/feuerrot/sfp-i2c" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/727a39d48b1eeb253de201ba59842a00_hu2ebec48fd5c983f2d020eb93e53c42cd_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/727a39d48b1eeb253de201ba59842a00_hu2ebec48fd5c983f2d020eb93e53c42cd_0_0x720_resize_q90_h2_box_3.webp' alt='Read the data of an SFP(&#43;)-module and print it pretty - feuerrot/sfp-i2c'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/feuerrot/sfp-i2c">GitHub - feuerrot/sfp-i2c: Read the data of an SFP(&#43;)-module and print it pretty</a>
        
        
            <p>Read the data of an SFP(+)-module and print it pretty - feuerrot/sfp-i2c</p>
        
    </div>
</blockquote>
        
    

<p>What's even better is that they are cheap. The SFP+ transceivers that I have are the <a target="_blank" href="https://www.dell.com/en-us/shop/c2g-finisar-ftlx8571d3bcl-compatible-10gbase-sr-mmf-sfp-transceiver-module-taa-sfp-transceiver-module-10-gigabi/apd/a8568835/pc-accessories">DELL FTLX8571D3BCL-FC</a>. I got them for around 8 EUR, but you can get them cheaper: here is an eBay listing for ten modules for 70 EUR.</p>

    
        

        
            








    


<blockquote class="embed embed-www_ebay_fr">
    
    <div class="embed-content">
        
            <a target="_blank" href="https://www.ebay.fr/itm/116299329366">https://www.ebay.fr/itm/116299329366</a>
        
        
    </div>
</blockquote>
        
    

<p>There are special ones for video that can handle weirder and faster signals with better signal integrity (12G-SDI for example) but from my testing, 3G SDI works just fine on these modules.</p>
<h3 id="sdi">
    SDI 
    
    <a class="header-link" href="#sdi">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>SDI (Serial Digital Interface) is a family of digital video interfaces first standardized by SMPTE. It's a widely used family of standards for transmitting uncompressed digital video signals over coaxial or fiber optic cables. It's primarily utilized in professional broadcasting and video production. SDI enables high-quality video transmission with low latency, making it ideal for live broadcasts and studio environments.</p>
<p>It supports a ton of formats 📼📀💾 described in the following table:</p>
<table>
<thead>
<tr>
<th>Standard</th>
<th>Name</th>
<th>Bitrates (Mbit/s)</th>
<th>Example video formats</th>
</tr>
</thead>
<tbody>
<tr>
<td><a target="_blank" href="https://en.wikipedia.org/wiki/SMPTE_259M">SMPTE 259M</a></td>
<td>SD-SDI</td>
<td>270, 360, 143, 177</td>
<td>480i, 576i</td>
</tr>
<tr>
<td><a target="_blank" href="https://en.wikipedia.org/wiki/SMPTE_344M">SMPTE 344M</a></td>
<td>ED-SDI</td>
<td>540</td>
<td>480p, 576p</td>
</tr>
<tr>
<td><a target="_blank" href="https://en.wikipedia.org/wiki/SMPTE_292M">SMPTE 292M</a></td>
<td>HD-SDI</td>
<td>1485 and 1485/1.001</td>
<td>720p, 1080i</td>
</tr>
<tr>
<td><a target="_blank" href="https://en.wikipedia.org/wiki/SMPTE_372M">SMPTE 372M</a></td>
<td>Dual Link HD-SDI</td>
<td>2970 and 2970/1.001</td>
<td>1080p60</td>
</tr>
<tr>
<td><a target="_blank" href="https://en.wikipedia.org/wiki/SMPTE_424M">SMPTE 424M</a></td>
<td>3G-SDI</td>
<td>2970 and 2970/1.001</td>
<td>1080p60</td>
</tr>
<tr>
<td><a target="_blank" href="https://pub.smpte.org/doc/st2081-10/20180102-pub/st2081-10-2018.pdf">SMPTE ST 2081</a></td>
<td>6G-SDI</td>
<td>6000</td>
<td>1080p120, 2160p30</td>
</tr>
<tr>
<td><a target="_blank" href="https://pub.smpte.org/latest/st2082-10/st2082-10-2018.pdf">SMPTE ST 2082</a></td>
<td>12G-SDI</td>
<td>12000</td>
<td>2160p60</td>
</tr>
<tr>
<td>SMPTE ST 2083</td>
<td>24G-SDI</td>
<td>24000</td>
<td>2160p120, 4320p30</td>
</tr>
</tbody>
</table>
<p>SDI uses uncompressed digital video and audio formats. The data is typically encoded in a YCbCr color space, with 10-bit or higher color depth, ensuring a high dynamic range and color fidelity. Alongside video, an SDI signal may contain up to 16, 48 kHz, 24-bit audio channels.</p>
<p>Data is encoded in NRZI format, and a linear feedback shift register is used to scramble the data to reduce the likelihood that long strings of zeroes or ones will be present on the interface. Thanks to NRZI encoding, the interface is self-synchronizing and self-clocking ⏱. SDI uses a form of forward error correction to maintain signal integrity, which is especially important for long-distance transmissions 🔭</p>
<p>On the physical layer side, SDI transmits signals using coaxial cables with BNC connectors, designed for minimal signal loss and interference. And as mentioned, it can also use fiber optic cables for longer distances and increased bandwidth.</p>
<p>See the Wikipedia page for more details:</p>

    
        

        
            








    


<blockquote class="embed embed-en_wikipedia_org">
    
        
            <a target="_blank" href="https://en.wikipedia.org/wiki/Serial_digital_interface" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/f9dab420d6eda97a6d6d2d5b78175e12_hua32efa77fca6fc02090e4c98a90394d5_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/f9dab420d6eda97a6d6d2d5b78175e12_hua32efa77fca6fc02090e4c98a90394d5_0_0x720_resize_q90_h2_box.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://en.wikipedia.org/wiki/Serial_digital_interface">Serial digital interface - Wikipedia</a>
        
        
    </div>
</blockquote>
        
    

<p>SDI is way more complicated to handle and most often requires an FPGA with specialized components, see this DIY pattern generator for example:</p>

    
        

        
            









<blockquote class="embed embed-hackaday">
    
        
            <a target="_blank" href="https://hackaday.com/2023/02/06/arduino-does-sdi-video-with-fpga-help/" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/b7801d7bf09d75cd80bb76c318900b29_hu4c3fc60dcaf1460f6a420532b3edffe4_0_0x720_resize_q90_h2_box_1.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/b7801d7bf09d75cd80bb76c318900b29_hu4c3fc60dcaf1460f6a420532b3edffe4_0_0x720_resize_q90_h2_box_1.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://hackaday.com/2023/02/06/arduino-does-sdi-video-with-fpga-help/">Arduino Does SDI Video With FPGA Help</a>
        
        
            <p>If you are running video around your home theater, you probably use HDMI. If you are running it in a professional studio, however, you are probably using SDI, Serial Digital Interface. [Chris Brown…</p>
        
    </div>
</blockquote>
        
    

<p>Thankfully, for this project, I don't actually need to decode/encode anything myself. Just to convert signals, which is generally cheap.</p>
<h2 id="first-prototype">
    First prototype 
    
    <a class="header-link" href="#first-prototype">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After this extensive research, It would seem that all that I really need is a cable equalizer for RX and a cable driver for TX to convert the 75-ohm single-ended and 100-ohm differential pairs 🤷‍♂️.</p>
<p>When I saw <a target="_blank" href="https://x.com/twi_kingyo">@twi_kingyo</a> post, I quickly found the <a target="_blank" href="https://www.ti.com/product/LMH0397">LMH0397,</a> which looked prefect at first glance. The description said <code>3G SDI bidirectional I/O with integrated reclocker</code>.</p>
<p>I got very excited 🤪 and quickly (too quickly) designed a PCB and sent it to production 🏭.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024_09_16_15-56-55_3rq8lhTxRE.png" >
            <img alt="LMH0397 Breakout prototype" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024_09_16_15-56-55_3rq8lhTxRE.png" />
        </a>
        
    </figure>

</p>
<p>I usually spend at least a week 📅 on a prototype. However, I treated this more as a breakout board than a proper prototype, which means I skimmed 🏄‍♀️ through plenty of things.</p>
<p>And it worked, well, partially worked.</p>
<h3 id="stupid-mistake">
    Stupid mistake 
    
    <a class="header-link" href="#stupid-mistake">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>In my haste, I didn't fully read the datasheet 🙄. While it is bidirectional in the sense that I can do both directions. It cannot, however, do it at the same time.</p>
<p>Something that I would have seen if I read the f-ing 🤬 datasheet:</p>
<blockquote>
<p>The LMH0397 is a 3G-SDI 75-Ω bidirectional I/O with
integrated reclocker. This device can be configured
<strong>either</strong> in <strong>input mode</strong> as an adaptive cable equalizer <strong>or</strong>
in <strong>output mode</strong> as a dual cable driver, allowing
system designers the flexibility to use a single BNC
either as an input or output port to simplify HD-SDI
video hardware designs.</p>
</blockquote>
<p>
    <figure>
        <a target="_blank" href="images/LMH0397_diagram.png" >
            <img alt="LMH0397 Simplified Block Diagram" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/LMH0397_diagram.png" />
        </a>
        
    </figure>

</p>
<p>If I had read things more carefully, I would have done things differently, but it worked and that meant I had a proof of concept.</p>
<h2 id="second-prototype">
    Second prototype 
    
    <a class="header-link" href="#second-prototype">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The second prototype was very cost minded 💰. My target was around 25eur per converter</p>
<p>The LMH0397 is a great chip, but it is 18.5 EUR per unit 💸 in low volumes and has redundant features not needed for a simple media converter.</p>
<p>I spent a while searching for an alternative. First thing I did is to take the reclocker out of the equation 🧮 which, while useful, isn't needed in most cases, especially at lower data rates. Now, I needed a simple cable equalizer and a cable driver. There are a few options, for instance <a target="_blank" href="https://www.ti.com/interface/serial-digital-interface/overview.html">Texas Instrument</a> or <a target="_blank" href="https://www.semtech.com/products/broadcast-video">Semtech</a> have some interesting chips.</p>
<p>But the ones I choose are from <a target="_blank" href="https://www.microchip.com/en-us/products/high-speed-networking-and-video/data-and-video-transceivers">Microchip</a>, the <a target="_blank" href="https://www.microchip.com/en-us/product/eqco30t5">EQCO30T5</a> and <a target="_blank" href="https://www.microchip.com/en-us/product/eqco30r5">EQCO30R5</a>.</p>
<p>While these chips are marketed as HD-SDI Transmitter/Receiver, they are capable of 3G-SDI Video. Interestingly 🤔, they also seem to be able to be used for a data back-channel and power over coax, but this won't be of any use for me.</p>
<h3 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The schematic (at least the interesting parts) is based on the typical application circuit of both schematics.
Compared to the LMH0397, the EQCO30T5 and EQCO30R5 are super simple to connect.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024_09_16_16-20-12_7CZVtUYs49.png" >
            <img alt="Version 2 prototype EQCO30T5 schematic" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024_09_16_16-20-12_7CZVtUYs49.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/chrome_2024_09_16_16-20-36_rW5pAelcwh.png" >
            <img alt="Version 2 prototype EQCO30R5 schematic" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024_09_16_16-20-36_rW5pAelcwh.png" />
        </a>
        
    </figure>

</p>
<h3 id="pcb">
    PCB 
    
    <a class="header-link" href="#pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>This is the first prototype with the appropriate dimensions, my plan was to fit everything into a 1U case (fiber splitter, converter modules, …). This means it has to be at most 45 mm tall, but I decided that my target would be 35 mm 📏. This would leave enough space for the chassis, mounting &amp; cables.</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024_09_16_16-16-49_d7eqsZauTm.png" >
            <img alt="Version 2 prototype render" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024_09_16_16-16-49_d7eqsZauTm.png" />
        </a>
        
    </figure>

</p>
<h3 id="first-test">
    First test 
    
    <a class="header-link" href="#first-test">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Once I received the PCBs, I immediately soldered four chips on two separate boards. Powered them up and amazingly there wasn't any magic smoke 🌋!</p>
<p>I then screwed in SMA to BNC adapters and connected my HDMI-SDI &amp; SDI-HDMI 🖥️ converters. Same as before, to avoid destroying an SFP+ module, I took a 1m DAC cable and connected the two boards.</p>
<p>And it worked! I got a picture on my monitor!</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/LH4b_dyFm0k"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<p>But I was far from done 😓.</p>
<h3 id="issues-for-longer-distances">
    Issues for longer distances 
    
    <a class="header-link" href="#issues-for-longer-distances">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Using a 1m DAC is fine and all, but the goal is at least a 50m distance.
To start things out, I used a 15m cable and as the video showed, it still worked. <em>Somewhat</em></p>
<p>I used Jellyfin to play videos on the output and mysteriously, while I had the window in the foreground everything was fine, no issues at all! But, as soon as I clicked elsewhere, the link crashed and would not come back up.</p>
<p>While the &quot;Sync&quot; LED indicated that it was trying to sync, using my 50m fiber cable didn't show any picture in any condition!</p>
<p>After some investigation, I realized that the only thing that changed in the source picture was the color of the title bar of the window:</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_19-44-08_b4e278cf-d052-4c7d-9922-73718d306164.png" >
            <img alt="Jellyfin on the foreground" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_19-44-08_b4e278cf-d052-4c7d-9922-73718d306164.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_19-43-23_8d4d4323-8308-45d8-8d58-1f6be50ee616.png" >
            <img alt="Jellyfin on the background" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_19-43-23_8d4d4323-8308-45d8-8d58-1f6be50ee616.png" />
        </a>
        
    </figure>

</p>
<p>After grabbing the pixel color (which was RGB <code>43, 43, 43</code>), I did a quick test with paint to try to reproduce the issue. And as you can see, it appears that this color is indeed part of the problem:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/a2gg0saaE4M"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<p>My best guess is that this color somehow translates to the gray (RGB <code>68, 68, 68</code> YCbCr <code>68, 128, 128</code>) part of the SDI pathological test pattern.</p>
<blockquote>
<p>The SDI pathological test pattern is specifically designed to stress test the SDI equalizer and PLL performance.
The test pattern consists of a static test image with the top half of the lines filled with a shade of magenta, and the bottom half of the lines filled with a shade of gray.</p>
</blockquote>

    
        

        
            









<blockquote class="embed embed-intel">
    
        
            <a target="_blank" href="https://www.intel.com/content/www/us/en/docs/programmable/683416/22-1/sdi-pathological.html" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/67fb93e1ed1890797d1934cb1ac44761_hu52d83c61ba9eb379e37040362ca3c030_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/67fb93e1ed1890797d1934cb1ac44761_hu52d83c61ba9eb379e37040362ca3c030_0_0x720_resize_q90_h2_box_3.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://www.intel.com/content/www/us/en/docs/programmable/683416/22-1/sdi-pathological.html">22.1.4. SDI Pathological</a>
        
        
    </div>
</blockquote>
        
    

<p>From what I understand, these colors basically produce long strings of zeros and ones 👨‍💻 in the data stream. This type of pattern is particularly challenging for SDI equalizers and PLLs to process and keep synced correctly.</p>
<p>At this point, I was sure that the issue was caused by a bad design on my part 🤦‍♂️ and feared that maybe the <code>EQCO30T5</code>/<code>EQCO30R5</code> were pushed too far with 3G SDI. However, I couldn't shake off the feeling that there might be something else at play here 🤔.</p>
<p>So instead of re-thinking my test setup, I went ahead and started working on the third prototype.</p>
<h2 id="third-prototype">
    Third prototype 
    
    <a class="header-link" href="#third-prototype">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Alright, so, let's dive into the third prototype 3️⃣.</p>
<p>The second prototype had many issues, the biggest one is, as demonstrated, the SDI pathological test patterns.
This was the main thing I wanted to tackle with this iteration, and I was hopeful that it would be the last one.</p>
<h3 id="changes">
    Changes 
    
    <a class="header-link" href="#changes">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<h4 id="sdi-equalizer">
    SDI equalizer 
    
    <a class="header-link" href="#sdi-equalizer">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>First off, I swapped out the <code>EQCO30R5</code> SDI equalizer for the <code>LMH0344</code>.</p>
<p>While both chips are designed for <code>SD-SDI</code> &amp; <code>HD-SDI</code> with the capability to go up to <code>3G-SDI</code>, the <code>EQCO30xx</code> chips seem not to be designed for this application but more to be used in the other direction.</p>
<p>On the other hand, the datasheet of the <code>LMH0344</code> talks way more about 3G-SDI. Alongside that, it also has a <code>Mute</code> pin and <code>Cable/Carrier detect</code> signaling pin.
It also mentions that the footprint is compatible with the <code>LMH0044</code>, <code>LMH0384</code> and <code>LMH0074</code>; It also states that it replaces both the <code>GS2974A</code> and <code>GS2974B</code> chips from Semtech.</p>
<p>This gives me a lot of flexibility in case I want to change chips in the future 📅.</p>
<p>But, as it turns out, the footprints of the <code>EQCO30R5</code> and <code>LMH0344</code> are extremely similar (at least the important parts), a few pins are marked DNC on the <code>EQCO30R5</code> and a capacitor is needed by the <code>LMH0344</code> where there isn't one for the <code>EQCO30R5</code>. <br>
Both of these issues can be solved by adding 0-ohm resistors that may or may not be placed during assembly.</p>
<p>In my head, this switch was good because the new component offered better performance and more flexibility 🤸‍♂️.</p>
<h4 id="power-supply">
    Power supply 
    
    <a class="header-link" href="#power-supply">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>During my tests, I noticed that the AMS1117 LDO regulator that I was using to provide 3.3v to the SFP+ module and other ICs was getting rather hot 🔥.</p>
<p>A quick search online + digging around in datasheets revealed the issue:</p>
<ul>
<li>The SFP+ module alone consumes around 500mA</li>
<li>The <code>EQCO30T5</code> typically consumes 45mA</li>
<li>The <code>EQCO30R5</code> typically consumes 55mA</li>
</ul>
<p>Which gives a total of 600mA ⚡.</p>
<p>A quick calculation for the max power dissipation of the <code>AMS1117</code> shows that current consumption is borderline over the limit, which explains why I had a furnace on my board 😂.</p>
<p>While I choose an LDO to reduce the risk of electrical noise producing more issues, this won't do. The end goal is to stack several of these boards together, which means that heat might cause issues.</p>
<p>Instead, I complicated things a bit more 🙄 and went with the <code>TPS5430</code>, a 3A step-down buck converter which integrates the MOSFET. The buck converter will provide a more efficient voltage regulation and can handle higher current demands without getting hot. The <strong>only</strong> reason I chose this model is that it's part of the &quot;Basic&quot; parts selection of JLCPCB which means I don't have to pay extra later.</p>
<p>But I recently used the <code>AP62200</code> for another project and I quite like this chip, mainly due to its size and the fact that it can accept 5V (something the TPS5430 doesn't 😥).</p>
<h4 id="fixed-layout">
    Fixed layout 
    
    <a class="header-link" href="#fixed-layout">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>The previous prototypes all had different size, different mounting hole locations, etc…</p>
<p>I wanted to change that, so I decided on a fixed layout ⚓ for every &quot;external&quot; component:</p>
<ul>
<li>SFP cage &amp; connector</li>
<li>Status LEDs</li>
<li>SDI In &amp; Out connectors</li>
<li>Mounting holes</li>
</ul>
<p>This resulted in something like this:</p>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_22-08-04_fc155cd8-8f01-482f-87c3-75233f837f9f.png" >
            <img alt="Fixed layout" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_22-08-04_fc155cd8-8f01-482f-87c3-75233f837f9f.png" />
        </a>
        
    </figure>

</p>
<p>I then decided that if I were to do another prototype, these components would not change location.</p>
<h3 id="pcb-1">
    PCB 
    
    <a class="header-link" href="#pcb-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_22-12-39_b545dc73-13aa-477c-b6a1-a626815b476d.png" >
            <img alt="Third prototype" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_22-12-39_b545dc73-13aa-477c-b6a1-a626815b476d.png" />
        </a>
        
    </figure>

</p>
<p>As you can see, I also added configuration pads for the SFP+ module and experimentally added a link between the LOS signal of the SFP to the output enable of the cable driver, I also added a link between the cable detect of the equalizer and the TXDIS of the SFP</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20240829_015459.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20240829_015459_hub69febd0b6cbe5af4601c150cff16c35_2563670_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20240829_015513.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20240829_015513_hueca7ed96a83700ce5a7f9b1a7e75c2fa_1983830_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20240829_015531.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20240829_015531_hu5d05f35d3764bdf782e443c0ab5e7c80_2160381_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h3 id="issues-for-longer-distances-still-present">
    Issues for longer distances still present 
    
    <a class="header-link" href="#issues-for-longer-distances-still-present">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I wish I had a video to show you but as I'll explain later, I don't have any V3 boards that are fully populated anymore</p>
<p>Even with all the improvements / changes I made in the third prototype, the performance issues were still a nagging problem. Using a 15m fiber cable worked, but extending that to even longer distances like 50 meters didn't work whatsoever. When I hooked up my setup to test it over this distance, there wasn't any picture at all on the receiving end.</p>
<h3 id="a-potential-solution">
    A potential solution 
    
    <a class="header-link" href="#a-potential-solution">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>While doing some tests, I realized that the very first prototype used the <code>LMH0397</code> which, while unidirectional, did include a reclocker ⏱.</p>
<p>After getting this board up and running again, I managed to get some partial success. The artifacts were completely gone with the 15m fiber 😃 and the 50m one now showed a picture but still had artifacts present in some cases 😕.</p>
<p>I was thinking that maybe if I had them on each side, it would work better, but that would raise the price so much. Instead, I decided that I would investigate a bit more before committing to this solution.</p>
<h3 id="oh-f----that-was-the-issue">
    Oh, f---, that was the issue! 
    
    <a class="header-link" href="#oh-f----that-was-the-issue">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>After spending countless hours tweaking my design and trying different configurations, I was about ready to pull my hair out. It seemed like, regardless of what changes I made, those pesky signal integrity issues persisted.</p>
<p>Then one day, while discussing this with some friends, we discussed the fact that it would be a good idea to have &quot;proper&quot; equipment to validate my design. Up to now, I used cheap-ish converters I bought off AliExpress, so I decided to buy the <a target="_blank" href="https://www.blackmagicdesign.com/fr/products/microconverters/techspecs/W-CONU-09">Micro Converter Bidirectional SDI/HDMI 3G</a> from Blackmagic.</p>
<p>After waiting a few days for the parcel to arrive, I proceeded to test it. To my surprise, everything worked, flawlessly, even with the 50m cable 📏.</p>
<p>I then tried using the cheap HDMI to SDI converter with the Blackmagic one acting as the receiver, and I could see that the lock indicator flickered. The other way around worked but showed the same symptoms as before.</p>
<p>In the end, most of these issues were caused by these cheap converters. Once I switched them out for higher-quality ones, everything clicked into place. The long-distance performance improved drastically, and the intermittent issues vanished.</p>
<p>This is when I realized two things:</p>
<ul>
<li>The converters I had are not suitable for long distances!</li>
<li>I had to buy new converters that are twice the price 💸.</li>
</ul>
<p>For fun, I also tried to chain every piece of fiber and coax I had ⛓️. This came to a total of 95m of fiber and 30m of coax, if this test was completed successfully, it would mean that the signal would have traveled 220m 🤔, and to my surprise, <strong>IT WORKED 😮🥳</strong></p>
<p>I also decided that I wasn't happy with the current version, mainly due to the LEDs and the fact that these prototypes were still using a 2-layer board, which is a hell of a sin with high-speed signaling.</p>
<h2 id="fourth-prototype--final-versions">
    Fourth prototype / Final versions 
    
    <a class="header-link" href="#fourth-prototype--final-versions">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Nothing significant changed in these versions.</p>
<p>The only things that changed are:</p>
<ul>
<li>SMT LEDs for status &amp; power indicators</li>
<li>Changed the footprint of the jumpers to allow floating configuration</li>
<li>Power input via the center mounting hole</li>
<li>Debug port with all the signals and I2C connections</li>
</ul>
<p>One thing that I also wanted to change was the connector. The ones I was using were 50-ohm SMA connectors, which are both the wrong impedance and connector for SDI.</p>
<p>As mentioned before, SDI uses 75-ohm cabling &amp; connectors, the most common of which are <a target="_blank" href="https://en.wikipedia.org/wiki/BNC_connector">BNCs</a>. That said, other connectors exist such as HD-BNC or <a target="_blank" href="https://en.wikipedia.org/wiki/DIN_1.0/2.3">DIN 1.0/2.3</a></p>
<p>Initially, I wanted to use proper BNC connectors, but after browsing Digikey I quickly found that they are SHOCKINGLY expensive 😮. Proper edge connectors from the likes of <a target="_blank" href="https://www.amphenolrf.com/">Amphenol RF</a> or others costs a minimum of 5 EUR per connector.</p>
<p>This is unacceptable for such a low-cost device.</p>
<p>Instead, I simply used the footprint of the <a target="_blank" href="https://www.amphenolrf.com/034-1030-12g.html">034-1030-12G</a> from Amphenol which is a 12G 75-ohm right angle HD-BNC connector. This means that these boards are technically compatible with a connector, but there's no way in hell I'm going to use it.</p>
<p>Instead, I'm buying <a target="_blank" href="https://aliexpress.com/item/1005003231536595.html">SDI male-male panel mount patch cables</a>, cutting them in half ✂️ and soldering them directly to the PCB.</p>
<p>According to the responses, I got from my question on electronics.stackexchange.com:</p>

    
        

        
            









<blockquote class="embed embed-electrical-engineering-stack-exchange">
    
        
            <a target="_blank" href="https://electronics.stackexchange.com/questions/724137/soldering-rg179-coaxial-cable-directly-to-pcb" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/5b1d8d5d53ceb0945851b61f9a2023d1_hu3328aabbce851b9e4320519515f28078_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/5b1d8d5d53ceb0945851b61f9a2023d1_hu3328aabbce851b9e4320519515f28078_0_0x720_resize_q90_h2_box_3.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://electronics.stackexchange.com/questions/724137/soldering-rg179-coaxial-cable-directly-to-pcb">Soldering RG179 coaxial cable directly to PCB</a>
        
        
            <p>I&#x27;m building a very low-cost bidirectional SDI-3G to fiber converter. I am in the process of selecting a connector for the SDI part. The issue is that I can&#x27;t really find one that won&#x27;t explode the </p>
        
    </div>
</blockquote>
        
    

<p>It should be perfectly fine to solder them directly.</p>
<p>One thing I did differently is that I made two versions of this prototype, one being the &quot;Normal&quot; version using the <code>EQCO30T5</code> and the &quot;Reclocked&quot; version using the <code>LMH0397</code></p>
<p>This was done for three reasons:</p>
<ul>
<li>One of the SDI links is the main output going to the IMAG, and while I can tolerate a camera going down due to signal integrity, I cannot accept the main output acting a way it shouldn't.</li>
<li>Debugging 🐛. Due to the way it's routed, I can technically use one of these boards as a poor's man eye diagram to help me show signal integrity issues. I could even use it as a repeater if I really wanted to.</li>
<li>I already had the chips for my previous prototype 🤷‍♂️.</li>
</ul>
<h3 id="normal-version">
    &quot;Normal&quot; version 
    
    <a class="header-link" href="#normal-version">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_23-15-08_3171187c-706b-4f03-ade8-a1dd6135469c.png" >
            <img alt="Fourth prototype - Normal version" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_23-15-08_3171187c-706b-4f03-ade8-a1dd6135469c.png" />
        </a>
        
    </figure>

</p>
<p>Here is a photo of it soldered to a BNC pigtail:

    <figure>
        <a target="_blank" href="images/IMG_20241028_225744.jpg" >
            <img alt="Fourth prototype - Normal version - Photo" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20241028_225744_huf617d0133d519002088b2f75afa5a0d3_3072590_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h3 id="reclocked-version">
    &quot;Reclocked&quot; version 
    
    <a class="header-link" href="#reclocked-version">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/chrome_2024-11-03_23-14-53_05be8a04-22af-4900-90d1-81f8cbbd9c1f.png" >
            <img alt="Fourth prototype - Reclocked version" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/chrome_2024-11-03_23-14-53_05be8a04-22af-4900-90d1-81f8cbbd9c1f.png" />
        </a>
        
    </figure>

</p>
<p>Here is a photo of it soldered, unfortunately the rest of the pigtails and SFP cages hadn't arrived yet:

    <figure>
        <a target="_blank" href="images/IMG_20241028_225732.jpg" >
            <img alt="Fourth prototype - Reclocked version - Photo" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20241028_225732_hu2f054a865e5b0a627ec461ceaa1105dd_3293701_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="assembly">
    Assembly 
    
    <a class="header-link" href="#assembly">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Fortunately, making 4 prototypes means that I actually had almost enought chips to fully assemble everything, so I went to work.
After a few hours, I was done with what I had.</p>
<p>
    <figure>
        <a target="_blank" href="images/IMG_20241028_225721.jpg" >
            <img alt="Assembly line" src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20241028_225721_hu90aab4497b861a7d288fd8b57a856ebc_2005186_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>When I designed the PCBs, I originally wanted to use standoffs to carry current. But when assembly time came, I realized that that would be a nightmare to disassemble 😫. So, to solve this, I decided to create a custom 3D-printed mount that could house eight of these converters (with only 6 slots populated, two reclocked bidirectional and four unidirectional normal ones).</p>
<p>This setup ensured that all components were neatly organized and securely mounted. The 3D-printed base also includes integrated power distribution 🔌 with JST connectors, making it easy to plug in &amp; out each converter for troubleshooting without any messy wiring.</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20241116_225348.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20241116_225348_hu7f1dd19d98137d7174a4bfe3a9644c87_1753675_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20241116_225402.jpg">
                            <img src="/diy-opensource-bidirectional-sdi-to-fiber-converter/images/IMG_20241116_225402_hubce321aaf659ff68e9d95ef0fa85a973_3038045_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Unfortunately, I haven't got a 1U rackmount case to show yet 😥 because I'm not fully committed to the SDI-only stage box design that these converters will live in, but I'll either make a new post or update this one once done ✍.</p>
<p>As for the price, without including the time it took to design and assemble them, I would say that I reached my price target by a small enough margin 🤑.
But what I do realize is that if I had to pay someone to design this, it wouldn't be economical, at all 💸. And suddenly the 155 EUR price of the Blackmagic converter (which can do 12G-SDI) seems much more reasonable, I would even say cheap!</p>
<p>That being said, what a journey! From the partial success of the initial prototype to the very frustrating issues with long-distance signal integrity 🤬, I learned some valuable lessons along the way 🚀.</p>
<p>Looking back, the most important takeaways are:</p>
<ul>
<li><strong>Testing equipment matters</strong>, when dealing with unknown &amp; difficult stuff that requires external components, always prioritize the quality of the equipment you use to validate your design, even if pricier 💰. In that spirit, I'll probably upgrade a bunch of equipment in my lab.</li>
<li><strong>Paying attention to detail</strong>, be it reading datasheets more carefully 🔍 or properly tweaking a design. Every little detail counts for a production tool.</li>
<li><strong>Iterative design</strong> isn't something I generally do, mostly due to budget constraints 💵. Except that in this instance, each prototype taught me something new, leading to better performance in the next iteration 🔄. I'll definitively do it more</li>
</ul>
<p>Here is the repo holding the schematic &amp; PCB for each version:</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/oshw-sdi3g-fiber" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/b404fa86c92e6ea0170f62cf11aed840_hue745c997b9b15a0b387499042497d8fd_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/b404fa86c92e6ea0170f62cf11aed840_hue745c997b9b15a0b387499042497d8fd_0_0x720_resize_q90_h2_box_3.webp' alt='An affordable and open source bidirectional 3G-SDI to Fiber converter. - TheStaticTurtle/oshw-sdi3g-fiber'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/oshw-sdi3g-fiber">GitHub - TheStaticTurtle/oshw-sdi3g-fiber: An affordable and open source bidirectional 3G-SDI to Fiber converter.</a>
        
        
            <p>An affordable and open source bidirectional 3G-SDI to Fiber converter. - TheStaticTurtle/oshw-sdi3g-fiber</p>
        
    </div>
</blockquote>
        
    

<p>While this project has come a long way from that first proof of concept, there's always room for improvement 🚀. The pro-AV space is exciting and constantly evolving, so there are plenty of opportunities to innovate. I'm already thinking about other projects 🤐 (audio over fiber, anyone?).</p>
<p>If you read this out to the end, first congrats 🎓, second, don't hesitate to reach out. I'm always down for a chat. Happy hacking! 🔧✨</p>

 ]]></content:encoded></item><item><title>AoDNS - The monstrosity that now exists</title><description> &lt;p>AoDNS, is a monumental monstrosity specifically designed to spread chaos among network engineers by streaming live audio via DNS queries&lt;/p></description><link>https://blog.thestaticturtle.fr/aodns-the-monstrosity-that-now-exists/</link><guid>https://blog.thestaticturtle.fr/aodns-the-monstrosity-that-now-exists/</guid><category> Audio</category><category> Experiments</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 30 Oct 2023 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/aodns-the-monstrosity-that-now-exists/images/cover.png"/><media:content url="https://blog.thestaticturtle.fr/aodns-the-monstrosity-that-now-exists/images/cover.png" medium="image"/><content:encoded><![CDATA[ <h1>AoDNS - The monstrosity that now exists</h1>
<span class="subtitle"><p>AoDNS, is a monumental monstrosity specifically designed to spread chaos among network engineers by streaming live audio via DNS queries</p></span>
<br>

    <img class="" src='/aodns-the-monstrosity-that-now-exists/images/cover_hue7db3bb5236f2644ea1fb8873055b468_611127_1350x900_fit_q80_bgffffff_box_3.jpg' alt="AoDNS - The monstrosity that now exists"/>

<hr>

<h2 id="why-">
    Why ???? 
    
    <a class="header-link" href="#why-">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The story about how I thought that tunneling audio over DNS and how I actually started working on it is idiotic.</p>
<p>A few weeks ago, I went on an 8h flight to the US for work 🛫. 3 Days before I reminded myself that I needed to find a solution for me to be able to listen to music on the plane.
My phone is pretty old, it's an OnePlus 5T 64g version, and, unfortunately, this model does not support SD cards and currently has less than 1g of free space, a complete pain.</p>
<p>I listen to all my music on Spotify and with less than a gigabyte of free space I wasn't about to download my playlist locally 😥.</p>
<p>Just as was about to leave work that day, I remembered that many airplanes (especially international ones) offer an internet service.
When you connect to their Wi-Fi it will ask you to either log in or purchase an internet plan and I thought, there's no way they are blocking DNS queries even if you don't pay 🤔.</p>
<p>That's when I thought, maybe, just maybe, if I were to encode audio and put it in DNS records I would be able to stream audio. I then messaged a few friends about this idea, and, as expected, they agreed that it was stupid and then left work and got in my car.</p>
<p>But then because my brain is weird, I couldn't stop thinking about how I could implement it, and at the end of the ride I had a pretty good idea how to implement AoDNS.</p>
<p>But then, my brain, still being peculiar, decided that it would be better to write code for 7h straight to write a proof of concept and go to bed at 4am while having work in the morning instead of going to sleep 💤.</p>
<h2 id="how-does-it-work">
    How does it work 
    
    <a class="header-link" href="#how-does-it-work">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I'll spare you all the details because between my 7h rush and the actual version, quite a few things changed and improved.</p>
<p>Most of the code is written in such a way that it would be straightforward to implement new functionalities like, for example, encryption 💪.</p>
<p>Here is how I made everything work:</p>
<h3 id="audio--compression">
    Audio &amp; Compression 
    
    <a class="header-link" href="#audio--compression">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I needed a simple way to get audio in and out and, as I wrote this in python I chose the easy way and used pyaudio. However, all the audio collection happens in a class that inherits from a base class, meaning that it would be trivial to add another way of inputting or outputting audio assuming that it supports arbitrary raw pcm frame lengths.</p>
<p>As raw pcm takes quite a bit of space, if I wanted to even have hope of making this work, I needed to compress/decompress it 📚, my default choice (and the one I stuck with) is to use opus. <a target="_blank" href="https://opus-codec.org/">Opus</a> is an open audio codec design for transmitting audio over the internet. It can use very low bitrates and a wide variety of sample rates &amp; frame sizes. As a bonus, it's super simple to use.</p>
<p>If the audio is voice only, <a target="_blank" href="http://www.rowetel.com/?page_id=452">Codec2</a> might also be an adequate option for even lower data usage. A quick calculation result in 163 bytes for 1sec of audio 😲.</p>
<p>Most codecs require a specific frame size, opus for example, is calculated like this: <code>60 * rate // 1000</code>. This is why it cannot be changed. Instead, it's queried during initial setup and re-used by the audio streamers.</p>
<p>What will adjust the amount of data collected is the sample rate and channel count. Theses are configurable when starting the example.</p>
<p>My goal for this project was to get 8kHz 1ch working. As you can see in the following table, compressing data to Opus is pretty good, in general, the output is only 6% of the input.
One thing to note is that, for Opus, the size of the compressed data isn't fixed and varies following the input signal 🔊.</p>
<table>
<thead>
<tr>
<th>Codec</th>
<th>Activity</th>
<th>Sample rate</th>
<th>Channels</th>
<th>Data size - PCM</th>
<th>Data size - Compressed</th>
<th>Ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Opus</td>
<td>❌</td>
<td>8000 Hz</td>
<td>1</td>
<td>960 bytes</td>
<td>39 bytes</td>
<td>~0.04</td>
</tr>
<tr>
<td>Opus</td>
<td>❌</td>
<td>8000 Hz</td>
<td>2</td>
<td>1920 bytes</td>
<td>93 bytes</td>
<td>~0.048</td>
</tr>
<tr>
<td>Opus</td>
<td>❌</td>
<td>16000 Hz</td>
<td>1</td>
<td>1920 bytes</td>
<td>75  bytes</td>
<td>~0.039</td>
</tr>
<tr>
<td>Opus</td>
<td>❌</td>
<td>16000 Hz</td>
<td>2</td>
<td>3840 bytes</td>
<td>134 bytes</td>
<td>~0.034</td>
</tr>
<tr>
<td>Opus</td>
<td>✅</td>
<td>8000 Hz</td>
<td>1</td>
<td>960 bytes</td>
<td>52 bytes</td>
<td>~0.054</td>
</tr>
<tr>
<td>Opus</td>
<td>✅</td>
<td>8000 Hz</td>
<td>2</td>
<td>1920 bytes</td>
<td>120 bytes</td>
<td>~0.062</td>
</tr>
<tr>
<td>Opus</td>
<td>✅</td>
<td>16000 Hz</td>
<td>1</td>
<td>1920 bytes</td>
<td>94  bytes</td>
<td>~0.048</td>
</tr>
<tr>
<td>Opus</td>
<td>✅</td>
<td>16000 Hz</td>
<td>2</td>
<td>3840 bytes</td>
<td>231 bytes</td>
<td>~0.06</td>
</tr>
</tbody>
</table>
<h2 id="storing-the-data">
    Storing the data 
    
    <a class="header-link" href="#storing-the-data">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I said, the plan is to store the audio data in TXT queries. To keep everything compatible with the outside world, I choose to store the data as ASCII 👨‍💻. There's also some option to consider here:</p>
<table>
<thead>
<tr>
<th>Algorithm</th>
<th>Input size</th>
<th>Output size</th>
<th>Ratio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Base16</td>
<td>256 bytes</td>
<td>512 bytes</td>
<td>x2</td>
</tr>
<tr>
<td>Base32</td>
<td>256 bytes</td>
<td>416 bytes</td>
<td>x1.63</td>
</tr>
<tr>
<td>Base64</td>
<td>256 bytes</td>
<td>344 bytes</td>
<td>x1.34</td>
</tr>
<tr>
<td>Base85</td>
<td>256 bytes</td>
<td>320 bytes</td>
<td>x1.25</td>
</tr>
<tr>
<td>Base91</td>
<td>256 bytes</td>
<td>315 bytes</td>
<td>x1.23</td>
</tr>
</tbody>
</table>
<p>To maximize the amount of data I could store in one record, I decided to use base91. It's not too obscure and still does a pretty good job.</p>
<p>One issue I stumbled on early is that TXT strings are limited to a max of 255 characters. Which, for me, roughly equates to 200 bytes of data after the base91 step.</p>
<p>The DNS server can easily bypass this limitation by simply answering multiple TXT strings 😏. My local responder (Windows server 2022) could handle around 3 KB of data in one record before it simply stopped responding.</p>
<p>One issue that arises when sending multiple strings is that the first answer you get from the responder might be ordered correctly, but, at least with Windows server, the second one won't be and instead will be in a randomized&lt; order 😭.</p>
<p>To solve this, I added an incrementing prefix to each string. The prefix is separated from the actual data by using <code>'''</code> which is one ASCII char that is not used in the base91 table.</p>
<p>After all of these steps, you can query a record and get a response like this one:</p>
<pre tabindex="0"><code class="language-dig" data-lang="dig"># dig +ttlunit TXT seq_49680.music.example.com

;; ANSWER SECTION:
seq_49680.music.example.com. 21s IN TXT  &#34;003&#39;&#39;&#39;8U|7E/J`Cc82|8XLpLk^T__5uf~F8PN7~Mfa(E^&gt;WsjNBHV(&lt;2AF|H&lt;us_*}Dyt68)v%:^`&lt;w?&amp;OxM}_ZUf6S&amp;KTbCtFQp$sD!]*a\&#34;&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;001&#39;&#39;&#39;8U{OOU8]UVh.PiU!N=6WJ3k@&gt;+fl&amp;&amp;&lt;`&gt;SnpY4Tzo_%W(Z_rs)@jzY1x|abYVng=d^vUg5cY\&#34;+K~@OsihU3cxIyz+&amp;MMQu!m,&gt;=dHbsaD&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;004&#39;&#39;&#39;rXSKF[23x}`Z!lP@HP8lDSWPhw^ViX!^CZHUgV9m=p}~kb5yk89@f*4/%Kk_!c2[{|\&#34;d,~?B:u|RltK]Ce]t0zf5S{h6H;W&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;005&#39;&#39;&#39;8Uy]!Pn!e%1J&amp;xz;V,~d@7J%Ca#ct]D7B^U?u4|],2IE]7ZuE$9;2n;V~{UmwB8P;)=cU/LSdxGvNn&gt;{PoBFUHx:&amp;4]jrS^aDu{ve7)\&#34;EU&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;006&#39;&#39;&#39;8Uy]jZ/.]MJLfG%lrc[5FJuJ8$Ixk9VfRjLsLg*Hc`P!x1sHSZpIv5%Rf%|DfXm]%Lr}B%$lRn1oYmOZ+C|sM9bZi6&amp;H+$NipeyS&lt;&gt;|bVvT&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;007&#39;&#39;&#39;8Uq&amp;l&lt;\&#34;!uR/i})@E]&lt;TsXZ%0&amp;ulQ%YB|p97tmV)mA|8[+M3;~G`XSu*cKo^Yg`Mq:JAiR{W*x`9mHe&gt;e&amp;?LfsL1!boJ^&amp;\&#34;[PPER0.(&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;008&#39;&#39;&#39;rX%=qo9rl2P@`BC!&gt;T,O&gt;(+ZU?hp}]WQT^MGki99SL18Ho[!yUL4]6jgEM*5eqkhMLs}fIwx&amp;5N*^&amp;XJv#0zG:5@vtC8|&gt;[cO(.U_^WeFNFB&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;009&#39;&#39;&#39;rX%=Ai]%`3:7z5swE1HMB9BS+7Wv@7\&#34;w+nYNl&amp;\&#34;M_Z*6=T&lt;p&gt;Zp?Kwo|M},B;$Uwf`c~UrkLbUDuk8H69%},V}X&gt;Ja~QRD&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;010&#39;&#39;&#39;rXKK!74bs7GWw.mBS/@NVj7gCgGy_@sv*9+]tTvB;6a|*N*6Lr.fDcyb&amp;sH2(f=_#zV?^Tb@H)De]&lt;j^.4?3])hA&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;011&#39;&#39;&#39;rXSK]\&#34;hAk4R.zCnc{\&#34;;RBm[~,Kgl`kB]sxx^b=9PW&amp;n.sK~1MWbOd[E:IdL`6Y%*3@&gt;?`M2Sh^cmAH~3]3;[uJl`&gt;5ITZUK5\&#34;m!C&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;012&#39;&#39;&#39;rX%=M5c+YU5JS`di#g7xy=;n~TrBLt*5FFhWpQ\&#34;S#li//^[1xA*Wgsd]l+K~F1znI;&lt;y30[Y2!A\&#34;OlDY:d[)8_DAC&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;013&#39;&#39;&#39;rXn;Erky{T&gt;_HKk&lt;OV2UD?wyYfEWMgbsJ?/HV;nMW,]`&lt;O9Z*Wp[}&lt;Z0n0.J4(KmD:(by2/187?oOsuL3_3}bUE2`xNjX[E1h4v%r\&#34;&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;014&#39;&#39;&#39;8U{O%;Nl&gt;EKbAo.w2?@(HJzsf(Ip9|Ac`rg1&gt;hS!7tS(Uw68%gO#8M(Vf7&gt;EZ.yN;zElg[Ga7(?_lMw%0y#d:oAa&amp;)|6T3A&gt;lB&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;015&#39;&#39;&#39;}AP;v[(v.gHQHsjlM/I{`60)WHD9^ia*&lt;Lgq&amp;5.iY9lM8$::#;zCp)06W&lt;lE&gt;xi9.T;]hd3$!aKaCmhG8G7wR#D[&lt;nZZJ&lt;WP$&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;016&#39;&#39;&#39;rX%=Nm;nn6uRG&lt;,JM:A5/sQJ~&gt;qB/&lt;o:iu^\&#34;sX]Z`n)oHv9lzqP%VEM5OsBW8LEP1mK\&#34;CLAcDrK.|8%s)~)V%h33Kr,8$ba|KvB&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;017&#39;&#39;&#39;rX$gRf8^ooNsOpLYL5Zy@(5%,j^*qG3K|L}/]G&amp;#^1#;o]mRRiH13zyw_^jZ#w2%s`6Qg.wQDR&lt;h?MAQ6!`43ov]+Ez&amp;75b&lt;?k0BH34QE&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;018&#39;&#39;&#39;8U+O@Nv/(&amp;nR|5&amp;kW$m6}laH`p&lt;Zp|LCx{t_lc1;!U6:.k!&gt;.7I8`&lt;y?*a#?(SvK@gRwV&amp;WYs@gdzNOHc4&lt;cst4&gt;cBHw[/oG*E9@FZ=CN&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;000&#39;&#39;&#39;8U{O#xvTKUK{a)`$*k0t`OwPP$B+(Ogky5*um7:PR2$UN1Q.{J!NZGWJ1~n=].^osw7&amp;qGSY=sw)]WptNCJ;fO4e}d4ybQIXTTt]on*E&#34;
seq_49680.music.example.com. 21s IN TXT  &#34;002&#39;&#39;&#39;8U4}l=%kj@6Ph3j9aZJO*qQ,2R*m2z[Kp.;h:%m6);/CR)8J4l}Im9IU5)H+KGLT4T`,(Jt}JwBmEqxMw7[pCUYPO,8&gt;T^9jl.[:w6)EG&#34;
</code></pre><p>The above example is 19 frames of 16kHz 1ch 16bit audio, which stores just a bit more than 1sec of audio 🎶. Perfect.</p>
<h2 id="generating-the-records">
    Generating the records 
    
    <a class="header-link" href="#generating-the-records">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As you saw in the <code>dig</code> query above, the subdomain is <code>seq_49680</code>. Obviously, storing 49680 sequences is impractical and would consume a shit ton of space on each resolver.</p>
<p>This is why there is a special subdomain called <code>sequence</code> that will respond with a single TXT string that holds all the available sequence numbers separated by a comma.</p>
<p>However, due to the max length of 255 imposed by TXT strings, the string is compressed with zlib and then base91 encoded.</p>
<p>The issue is that even with this protection, there is a possibility that if the server is left running for long enought, the sequence number would grow so high that it will still overflow and take too much space. This will cause the server to crash 😕.</p>
<p>As the sequence numbers are linear on the server side, this could be improved by sending the first sequence number available and number of available records.</p>
<h2 id="ttl">
    TTL 
    
    <a class="header-link" href="#ttl">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>TTL is a big issue, especially if the server is behind resolvers which might not respect low TTLs 🕖.</p>
<p>The TTL of the <code>sequence</code> record is fixed to 10 seconds. This is done to ensure that it will be somewhat up-to-date before the client will ever run out of sequences. While I have not tested this extensively, 10sec should be high enought to work somewhat decently on the public internet 😅.</p>
<p>On the other hand, the TTL of sequence subdomains is dynamically calculated to be around 80% of the total length of audio data stored in all the records. The formula looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>seq_duration <span style="color:#555">=</span> (codec_frame_size <span style="color:#555">*</span> frame_count) <span style="color:#555">/</span> (sample_rate <span style="color:#555">*</span> channels <span style="color:#555">*</span> <span style="color:#f60">2</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>ttl <span style="color:#555">=</span> <span style="color:#366">int</span>(seq_duration <span style="color:#555">*</span> sequencer<span style="color:#555">.</span>max_concurrent_numbers <span style="color:#555">*</span> <span style="color:#f60">0.8</span>)
</span></span></code></pre></div><p>Which at 16kHz, 1 channel and a queue of 75 sequence domains in the generator gives a TTL of around 23sec.</p>
<h2 id="demo">
    Demo 
    
    <a class="header-link" href="#demo">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Here is a demo of AoDNS running a 16kHz 1 channel:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/OxLp7QJcwbs"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>While this project was done purely as a tinkering experiment to test the feasibility of using DNS as a non-standard mechanism to move data around, I think it turned out pretty well 😃.</p>
<p>One thing that I didn't discus yet and what a friend mentioned to me, is the potential for free speech and public communication 👨‍👩‍👧‍👦. If the server chain is properly implemented and extended with encryption &amp; DNSSEC &amp; DoTLS or DoHTTPS, I think it would be pretty difficult to actually censor / modify the audio feed.  If the validity of the responses is checked correctly, the worst possible outcome would be a DoS 🔇.</p>
<p>I think that it would be easier to find a DNS server that resolves to the full internet (which could be forwarded to AoDNS the server)  rather than trying to establish TCP sessions on filtered networks 🌐. Especially since the traffic will look like normal DNS queries.</p>
<p>Since setting up DNS resolvers is mostly effortless, you could even set up many to act as load balancers / region separator.</p>
<p>This is of course pretty far-fetch and far from reality. But, overall pretty happy with this project 😄.</p>
<p>Note that when I finished the code I was less than 12h from my flight and still didn't have a solution 😅. (Ended up flashing the latest lineage on my old phone and installed Spotify 🤷‍♂️)</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/AoDNS" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/32cd44c511248b409c189eb678f59795_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/32cd44c511248b409c189eb678f59795_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='AoDNS, or Audio over DNS is a method of streaming audio data with TXT queries.  - GitHub - TheStaticTurtle/AoDNS: AoDNS, or Audio over DNS is a method of streaming audio data with TXT queries.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/AoDNS">GitHub - TheStaticTurtle/AoDNS: AoDNS, or Audio over DNS is a method of streaming audio data with TXT queries.</a>
        
        
            <p>AoDNS, or Audio over DNS is a method of streaming audio data with TXT queries.  - GitHub - TheStaticTurtle/AoDNS: AoDNS, or Audio over DNS is a method of streaming audio data with TXT queries.</p>
        
    </div>
</blockquote>

 ]]></content:encoded></item><item><title>Changing my blogging platform</title><description> &lt;p>For a while now, I used ghost for my blog platform. A while ago, I decided that enough was enought I was going to migrate to another platform.
Hugo was selected and lots of development occured.&lt;/p></description><link>https://blog.thestaticturtle.fr/changing-blogging-platforms/</link><guid>https://blog.thestaticturtle.fr/changing-blogging-platforms/</guid><category> Web</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 08 Aug 2023 10:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/changing-blogging-platforms/images/cover.png"/><media:content url="https://blog.thestaticturtle.fr/changing-blogging-platforms/images/cover.png" medium="image"/><content:encoded><![CDATA[ <h1>Changing my blogging platform</h1>
<span class="subtitle"><p>For a while now, I used ghost for my blog platform. A while ago, I decided that enough was enought I was going to migrate to another platform.
Hugo was selected and lots of development occured.</p></span>
<br>

    <img class="" src='/changing-blogging-platforms/images/cover_hu3571fcec0e3ddc2f55770fc42c67b9a3_25653_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Changing my blogging platform"/>

<hr>

<p>For a while now, I used ghost for my blog platform. And, honestly, ghost is a remarkable piece of software, by far the best one I was able to find when I originally set up my blog.</p>
<p>It has some issues though, the biggest one for me is that while it does not natively support markdown and instead uses a custom JSON format.</p>
<p>This is an issue as I write <strong>all</strong> my posts in markdown. I find it way more convenient and easy to use for the kind of blogging I do than using ghost text editor. It does somewhat convert when copying and pasting, but not everything gets translated (especially code blocks).</p>
<p>This is when I decided to migrate to another platform.</p>
<p>At first, as a stupid software engineer, I tried to build my own blog platform with Django. You can probably guess that it never saw the light of day. It's still sitting in my internal git server, half working.</p>
<p>A good while ago, I decided that enought was enought I was going to make this change happen. I laid out a list of requirements for my new website (blog and portfolio).:</p>
<ul>
<li>Static, don't want to deal with a database anymore</li>
<li>No JS except for giscus for comments.</li>
<li>Gets ALL information from Markdown files.</li>
</ul>
<p>I tested countless static site generators (even tried to build my own). In the end, I chose Hugo.</p>
<h2 id="the-move">
    The move 
    
    <a class="header-link" href="#the-move">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I spent a lot of time writing a Hugo theme for this new website, and to be honest, I still don't understand how certain things work.</p>
<p>You can find the source code here:

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/website_theme" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/04f754703b7593ac04b1a1f28bdaf5fc_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/04f754703b7593ac04b1a1f28bdaf5fc_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Theme for the thestaticturtle.fr website. Contribute to TheStaticTurtle/website_theme development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/website_theme">GitHub - TheStaticTurtle/website_theme: Theme for the thestaticturtle.fr website</a>
        
        
            <p>Theme for the thestaticturtle.fr website. Contribute to TheStaticTurtle/website_theme development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/website_blog" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/37ba4d74401c46f72335506934c9f6dc_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/37ba4d74401c46f72335506934c9f6dc_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/website_blog development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/website_blog">GitHub - TheStaticTurtle/website_blog</a>
        
        
            <p>Contribute to TheStaticTurtle/website_blog development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/website_portfolio" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/643882935ee869c75e5b7bee08089032_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/643882935ee869c75e5b7bee08089032_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/website_portfolio development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/website_portfolio">GitHub - TheStaticTurtle/website_portfolio</a>
        
        
            <p>Contribute to TheStaticTurtle/website_portfolio development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    
</p>
<p>Two difficult things to do were image handling (mostly because I wanted to compress remote images as well as store them locally) and diagrams (because HUGO doesn't support on-build diagrams, only with JS).</p>
<p>But by far the most difficult one was to generate embed for links. Normally, it would pretty easy to parse out a web page and extract the opengraph tags. Try doing it in a go template 😭.
Took a while, but with some patience and many regexes I got it to generate nice embeds at build time.</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>To conclude this tiny post, I'm pleased with this new solution and thanks to github actions it makes it even easier to write the blog posts easily.</p>
<p>The most important thing is that you'll probably never see any further adjustments if I migrate again, since I wrote the whole theme myself, and I'm pleased with it. It's mostly SCSS files and a few templates, so fairly easy to migrate to somewhere else.</p>

 ]]></content:encoded></item><item><title>Mix this wave: DIY audio mixer</title><description> &lt;p>Making a very basic 3CH audio mixer with amplified output&lt;/p></description><link>https://blog.thestaticturtle.fr/mix-this-wave-diy-audio-mixer/</link><guid>https://blog.thestaticturtle.fr/mix-this-wave-diy-audio-mixer/</guid><category> Diy</category><category> Electronics</category><category> Audio</category><dc:creator> Samuel</dc:creator><pubDate>Sat, 01 Jul 2023 10:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/mix-this-wave-diy-audio-mixer/images/cover_huf2bc9868bdc2d18c7ed7175e16271027_207771_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/mix-this-wave-diy-audio-mixer/images/cover_huf2bc9868bdc2d18c7ed7175e16271027_207771_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Mix this wave: DIY audio mixer</h1>
<span class="subtitle"><p>Making a very basic 3CH audio mixer with amplified output</p></span>
<br>

    <img class="" src='/mix-this-wave-diy-audio-mixer/images/cover_huf2bc9868bdc2d18c7ed7175e16271027_207771_1350x900_fit_q80_box.jpg' alt="Mix this wave: DIY audio mixer"/>

<hr>

<p>I'm working in the backstage of concerts of our local choir. We have an intercom system, it's based on the Clear Com party line system, but that's a story for another time.</p>
<p>My issue is that I need to hear the audio from my computer and the audio from the intercom, and I guess that my phone would be a nice idea too. I didn't want something complicated like pan control / EQs.</p>
<p>So to recap, I need:</p>
<ul>
<li>3 Stereo inputs with individual volume control</li>
<li>1 Output with amplified output for my headphones</li>
</ul>
<p>It's been a while since I did some manual soldering on a proto board, and I wanted to avoid waiting 2 weeks for a PCB.
So, I went to Farrell and Amazon and ordered a few things:</p>
<ul>
<li>3x 10kOhm Log potentiometers</li>
<li>DC Input jacks</li>
<li>PAM8403 Amplifier boards</li>
<li>4x 6.35 mm TRS Jacks</li>
</ul>
<h2 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The schematic is heavily inspired by this project <a target="_blank" href="https://www.sonelec-musique.com/electronique_realisations_melangeur_audio_002.html">https://www.sonelec-musique.com/electronique_realisations_melangeur_audio_002.html</a>, but with the PAM8403 replacing the op amp.

    <figure>
        <a target="_blank" href="images/dl_chrome_2023-02-05_22-07-09_48c11ddf-093a-40d2-8d8d-12212a1c090f.png" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_chrome_2023-02-05_22-07-09_48c11ddf-093a-40d2-8d8d-12212a1c090f.png" />
        </a>
        
    </figure>

</p>
<h2 id="soldering">
    Soldering 
    
    <a class="header-link" href="#soldering">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00678.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00678_hu93d416174c0ef56c1e459ae987aa4159_393462_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_DSC00682.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00682_hu4f2db7e1cdb75fbc6c568adb5aa51961_486820_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


And now I remember why I design pcb instead of manually soldering them.</p>
<h2 id="case">
    Case 
    
    <a class="header-link" href="#case">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00676.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00676_hubf9132fbe90972e4cc6572ffdc1e0f1a_416317_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="assembly">
    Assembly 
    
    <a class="header-link" href="#assembly">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="adding-jack-connectors">
    Adding jack connectors 
    
    <a class="header-link" href="#adding-jack-connectors">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00689.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00689_hud8ecd27d7a1e2c24b7e05e0a4977f922_432381_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h3 id="adding-power-connector">
    Adding power connector 
    
    <a class="header-link" href="#adding-power-connector">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00693.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00693_hu4978aebb7a4f62d37d3ec9e928658d69_366904_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h3 id="placing-the-pcb">
    Placing the PCB 
    
    <a class="header-link" href="#placing-the-pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00697.JPG" >
            <img alt="" src="/mix-this-wave-diy-audio-mixer/images/dl_DSC00697_huc3634409eb67fb78da8d3cdf2d8d7e29_519583_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="final-photos">
    Final photos 
    
    <a class="header-link" href="#final-photos">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/DSC00714.JPG">
                            <img src="/mix-this-wave-diy-audio-mixer/images/DSC00714_hu1cf7b353a99acf9d697c4a11eefd1164_442198_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/DSC00719.JPG">
                            <img src="/mix-this-wave-diy-audio-mixer/images/DSC00719_huf3850fadc1bfc062ac7c8edf91cbc969_470867_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/DSC00721.JPG">
                            <img src="/mix-this-wave-diy-audio-mixer/images/DSC00721_hufae465d850bbfdd4bb2daff1c8975124_491678_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="final-thoughts">
    Final thoughts 
    
    <a class="header-link" href="#final-thoughts">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Overall happy with this, not the greatest sound quality, especially because the PAM8403 isn't designed for headphones but it works.</p>

 ]]></content:encoded></item><item><title>Monitoring CO2 levels, the DIY way</title><description> &lt;p>Building a DIY CO2 and PM2.5 sensor to measure the air quality in my office and improve my home-maker life.&lt;/p></description><link>https://blog.thestaticturtle.fr/monitoring-co2-levels-the-diy-way/</link><guid>https://blog.thestaticturtle.fr/monitoring-co2-levels-the-diy-way/</guid><category> Diy</category><category> Electronics</category><category> Sensors</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 01 Jun 2023 10:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/monitoring-co2-levels-the-diy-way/images/cover_hu2d72f9422df7578d7b8915eaa2d77584_197514_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/monitoring-co2-levels-the-diy-way/images/cover_hu2d72f9422df7578d7b8915eaa2d77584_197514_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Monitoring CO2 levels, the DIY way</h1>
<span class="subtitle"><p>Building a DIY CO2 and PM2.5 sensor to measure the air quality in my office and improve my home-maker life.</p></span>
<br>

    <img class="" src='/monitoring-co2-levels-the-diy-way/images/cover_hu2d72f9422df7578d7b8915eaa2d77584_197514_1350x900_fit_q80_box.jpg' alt="Monitoring CO2 levels, the DIY way"/>

<hr>

<p>As I spend most of my day in my office 🤓, I thought it would be a good idea to measure the CO2 levels of the room. CO2 isn't as dangerous as CO, but it can still pose some issues if the levels are too high.
And after being in a room that hasn't any ventilation for +10h straight, the CO2 level is definitely high.</p>
<p>This <a target="_blank" href="https://newscenter.lbl.gov/2012/10/17/elevated-indoor-carbon-dioxide-impairs-decision-making-performance/">study</a>, found that the level of CO2 has a very negative effect on performance, particularly on thinking in general:

    <figure>
        <a target="_blank" href="images/dl_CO2-Figure2-624x455.png" >
            <img alt="CO2 Levels on human activities from a study of the Lawrence Berkeley National Laboratory" src="/monitoring-co2-levels-the-diy-way/images/dl_CO2-Figure2-624x455.png" />
        </a>
        
    </figure>


Which is sadly something that I noticed before after a long day 😕.</p>
<p>I also wanted to measure the PM2.5 concentration, as it could prove useful for automations down the road.</p>
<p>My office does have a window and, in summer, it's pretty easy, as I leave it open all the time. In winter, however, the room gets cold quickly. Which is why I wanted a sensor that can tell me if I <strong>need</strong> to open the window.</p>
<h2 id="options">
    Options 
    
    <a class="header-link" href="#options">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So, what's available out there? Well, first of, I want something that won't send everything to some cloud that I don't control. That reduce the options quite a bit.</p>
<p>IMO, the Aranet4 from Aranet is the best sensor if I wanted something pre-made. Looks very well-made and is compatible with HomeAssistant. However, it's pretty pricey.</p>
<p>As I also wanted to measure the quantity of particulates floating in the air and I couldn't find a sensor that measured both that was at low enought price.</p>
<p>It meant DIY 🧐!</p>
<h2 id="diy">
    DIY 
    
    <a class="header-link" href="#diy">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>First, what do I actually need. I already have temperature and humidity from a Mi-Thermometer, I don't care much about the light level. What I do care about is:</p>
<ul>
<li>CO2</li>
<li>PM2.5 / PM1.0 / PM10</li>
</ul>
<p>After reading a few blog posts and watching a few videos, I choose to use the <a target="_blank" href="https://data.thestaticturtle.fr/ShareX/2022/12/30/MH-Z19%20CO2%20Ver1.0.pdf">MH-Z19</a>, it can measure the CO2 concentration in ppm from 0 to 5000ppm and the temperature and output it to either serial or PWM. Seems perfect for my needs.</p>
<p>For the particulate matter sensor, I choose a <a target="_blank" href="https://data.thestaticturtle.fr/ShareX/2022/12/30/plantower-pms5003-manual_v2-3.pdf.">PMS5003</a> which gives me PM2.5, PM1.0 and PM10</p>
<h2 id="hardware">
    Hardware 
    
    <a class="header-link" href="#hardware">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="3d">
    3D 
    
    <a class="header-link" href="#3d">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I first hoped into SolidWorks and my something that reassembles a case.</p>
<p>The PMS5003 has an intake fan in it and by default the air is ejected on the same side:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-12-30_23-44-20_bb5f5da0-bcc2-4ebc-82e3-c10159289503.png" >
            <img alt="PM5003 air pattern" src="/monitoring-co2-levels-the-diy-way/images/dl_chrome_2022-12-30_23-44-20_bb5f5da0-bcc2-4ebc-82e3-c10159289503.png" />
        </a>
        
    </figure>


That doesn't really work for me, as I also want the MHZ19 to use the same air. And while technically, you could find a position which would work, I wanted to avoid spending a lot of time on this. So, I simply opened it up, figured out the air pattern and drill holes on the other side.</p>
<p>So, here is what I came up with:

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2022-12-30_23-38-41_d8d4f269-68e7-4008-a343-ddbc36aaaa81.png" >
            <img alt="3D Model assembly" src="/monitoring-co2-levels-the-diy-way/images/_hu160dca306678e8ccfb69ab25eb55ee80_151341_9309bb0962817c09aee1f83578eb2ca9.webp" />
        </a>
        
    </figure>

</p>
<p>The air comes in from the side, ejected on the other side and then passes over the MHZ19.</p>
<p>On the front, I added a recess for a washer that I'll use a button using the esp32 touch library, a recess for the OLED and a hole for LEDs:

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2022-12-30_23-37-39_4bd7ef8d-89e9-40e5-b361-c4206d5f4822.png" >
            <img alt="Front of 3D model." src="/monitoring-co2-levels-the-diy-way/images/_huab7017f90e20c6139c359f9f49697c65_90903_0864e0b8fa9161a42cbf8222580ab6fa.webp" />
        </a>
        
    </figure>

</p>
<p>On one side I added an intake hole for the fan, an output, and a hole for the USB wire.</p>
<h3 id="assembly">
    Assembly 
    
    <a class="header-link" href="#assembly">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Assembly was a little finicky. I started by inserting the OLED:

    <figure>
        <a target="_blank" href="images/dl_DSC00216.JPG" >
            <img alt="OLED Installed." src="/monitoring-co2-levels-the-diy-way/images/dl_DSC00216_huc653422a77262f76f89c65c4727e5364_754186_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>Then poured hot glue in the LED hole to act as a diffuser and then put the LED strip in:

    <figure>
        <a target="_blank" href="images/dl_DSC00219.JPG" >
            <img alt="LEDs installed" src="/monitoring-co2-levels-the-diy-way/images/dl_DSC00219_hub36cf8cd59c6de00e23bdd6f4bb1f60f_752316_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>Unfortunately, my 3D print wasn't perfect and the PM5003 didn't fit by less than a millimeter. So, I used some hot air to ease it in 😏:

    <figure>
        <a target="_blank" href="images/dl_DSC00222.JPG" >
            <img alt="PMS5003 and MHZ19 installed" src="/monitoring-co2-levels-the-diy-way/images/dl_DSC00222_huc788694f64e39bd34688ea9f40661c81_691908_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>Then with a lot of patience, I manage to solder everything to the ESP32 and hot glued it in place:

    <figure>
        <a target="_blank" href="images/dl_DSC00627.JPG" >
            <img alt="ESP32 soldered in" src="/monitoring-co2-levels-the-diy-way/images/dl_DSC00627_hu11800146cae391d3308e5f39a6025019_829922_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>A bonus of the 3D print being slightly out of size, is that I can use the flat side of the PMS5003 to stick it to the wall with some double side tape.</p>
<h2 id="software">
    Software 
    
    <a class="header-link" href="#software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Usually, I would write my own firmware for the ESP32 that would publish the sensor data to my MQTT server and I would create the entities myself in HomeAssistant.</p>
<p>However, this time I chose to use ESPHome because it's way easier to use and integrates perfectly with HomeAssistant.</p>
<p>So let's get started. First thing to do is to set up esphome:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">esphome</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>office-air-quality<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">esp32</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">board</span>:<span style="color:#bbb"> </span>esp32dev<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">framework</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">type</span>:<span style="color:#bbb"> </span>arduino<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">logger</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">api</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">encryption</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">key</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;FTt4GyAtVoM8tIqBhNSW3QF+SND9NaIA97l1FkfTdGg=&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">    
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">ota</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">password</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;8ebf931b96986fa4d46fb35987957b3d&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>my_ota<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">  
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">wifi</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">ssid</span>:<span style="color:#bbb"> </span>!secret wifi_ssid<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">password</span>:<span style="color:#bbb"> </span>!secret wifi_password<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">ap</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">ssid</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Office-Air-Quality&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">password</span>:<span style="color:#bbb"> </span>!secret ap_password<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">captive_portal</span>:<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next is the interface setup, I need the two UARTs for the pms5003 and mhz19, the I2C for the OLED, the touch interface for the button and the fonts for the OLED</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">i2c</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>i2c_oled<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">sda</span>:<span style="color:#bbb"> </span><span style="color:#f60">18</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">scl</span>:<span style="color:#bbb"> </span><span style="color:#f60">19</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">uart</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>uart_pms5003<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">rx_pin</span>:<span style="color:#bbb"> </span>GPIO16<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">tx_pin</span>:<span style="color:#bbb"> </span>GPIO17<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">baud_rate</span>:<span style="color:#bbb"> </span><span style="color:#f60">9600</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>uart_mhz19<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">rx_pin</span>:<span style="color:#bbb"> </span>GPIO26<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">tx_pin</span>:<span style="color:#bbb"> </span>GPIO25<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">baud_rate</span>:<span style="color:#bbb"> </span><span style="color:#f60">9600</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">esp32_touch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">setup_mode</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">font</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">file</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;gfonts://Lato&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>lcdfont_big<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">size</span>:<span style="color:#bbb"> </span><span style="color:#f60">15</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">file</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;gfonts://Roboto&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>lcdfont_small<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">size</span>:<span style="color:#bbb"> </span><span style="color:#f60">12</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">file</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;gfonts://Roboto&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>lcdfont_verysmall<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">size</span>:<span style="color:#bbb"> </span><span style="color:#f60">10</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Then I need to add the sensors, fortunately ESPHome has platforms for the pms5003 and the mhz19. As I use an ESP32, I can also use the esp32_touch platform for the user button.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">sensor</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>pmsx003<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">uart_id</span>:<span style="color:#bbb"> </span>uart_pms5003<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">type</span>:<span style="color:#bbb"> </span>PMSX003<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pm_1_0</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>pmsx003_pm1_0<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Concentration particules &lt;1.0µm&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pm_2_5</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>pmsx003_pm2_5<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Concentration particules &lt;2.5µm&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pm_10_0</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>pmsx003_pm10_0<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Concentration particules &lt;10.0µm&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>mhz19<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">uart_id</span>:<span style="color:#bbb"> </span>uart_mhz19<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">co2</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>mhz19_co2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Capteur CO2&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">temperature</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>mhz19_temp<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">internal</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">update_interval</span>:<span style="color:#bbb"> </span>30s<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">automatic_baseline_calibration</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">binary_sensor</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>esp32_touch<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">internal</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Button&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pin</span>:<span style="color:#bbb"> </span>GPIO27<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">threshold</span>:<span style="color:#bbb"> </span><span style="color:#f60">1000</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">on_press</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">display.page.show_next</span>:<span style="color:#bbb"> </span>oled<span style="color:#bbb">
</span></span></span></code></pre></div><p>Next are the outputs, I used a small OLED with 3 pages with the only thing changing being the PM2.5 / PM1.0 / PM10, CO2 is always displayed. And the light with the <code>internal</code> key to hide it from HomeAssistant.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">display</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>ssd1306_i2c<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>oled<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">model</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;SSD1306 128x32&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">address</span>:<span style="color:#bbb"> </span>0x3C<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pages</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">lambda</span>:<span style="color:#bbb"> </span>|-<span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#c30;font-style:italic">          it.print(0, 0 , id(lcdfont_big), &#34;CO2  &#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#c30;font-style:italic">          it.print(0, 15, id(lcdfont_big), &#34;PM2.5&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#c30;font-style:italic">          it.print(54, 1 , id(lcdfont_small), String((int)(id(mhz19_co2).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#c30;font-style:italic">          it.print(54, 17, id(lcdfont_small), String((int)(id(pmsx003_pm2_5).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#c30;font-style:italic">          it.print(92, 3 , id(lcdfont_verysmall), &#34;ppm&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#c30;font-style:italic">          it.print(92, 18, id(lcdfont_verysmall), &#34;ug/m3&#34;);</span><span style="color:#bbb">          
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">lambda</span>:<span style="color:#bbb"> </span>|-<span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#c30;font-style:italic">          it.print(0, 0 , id(lcdfont_big), &#34;CO2  &#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#c30;font-style:italic">          it.print(0, 15, id(lcdfont_big), &#34;PM1.0&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#c30;font-style:italic">          it.print(54, 1 , id(lcdfont_small), String((int)(id(mhz19_co2).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#c30;font-style:italic">          it.print(54, 17, id(lcdfont_small), String((int)(id(pmsx003_pm1_0).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#c30;font-style:italic">          it.print(92, 3 , id(lcdfont_verysmall), &#34;ppm&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#c30;font-style:italic">          it.print(92, 18, id(lcdfont_verysmall), &#34;ug/m3&#34;);</span><span style="color:#bbb">          
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">lambda</span>:<span style="color:#bbb"> </span>|-<span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#c30;font-style:italic">          it.print(0, 0 , id(lcdfont_big), &#34;CO2  &#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#c30;font-style:italic">          it.print(0, 15, id(lcdfont_big), &#34;PM10&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#c30;font-style:italic">          it.print(54, 1 , id(lcdfont_small), String((int)(id(mhz19_co2).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#c30;font-style:italic">          it.print(54, 17, id(lcdfont_small), String((int)(id(pmsx003_pm10_0).state)).c_str());
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#c30;font-style:italic">          it.print(92, 3 , id(lcdfont_verysmall), &#34;ppm&#34;);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#c30;font-style:italic">          it.print(92, 18, id(lcdfont_verysmall), &#34;ug/m3&#34;);</span><span style="color:#bbb">          
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">light</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>status_led<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">internal</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>neopixelbus<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">variant</span>:<span style="color:#bbb"> </span>WS2811<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">pin</span>:<span style="color:#bbb"> </span>GPIO33<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">num_leds</span>:<span style="color:#bbb"> </span><span style="color:#f60">9</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">type</span>:<span style="color:#bbb"> </span>GRB<span style="color:#bbb">
</span></span></span></code></pre></div><p>Thankfully, esphome automatically updates the OLED content. Which is not the case of the LEDs, so I used the SNTP platform to execute a script every 1 second.</p>
<p>The script does the following:</p>
<ul>
<li>Creates a call to turn on the LEDs</li>
<li>With a transition of 100ms from the previous state</li>
<li>And set the color based on the CO2 level:
<ul>
<li>&gt;= 1500 → Purple</li>
<li>&gt;= 1200 → Red</li>
<li>&gt;= 900 → Orange</li>
<li>&gt;= 650 → Green</li>
<li>&lt; 650 → Blue</li>
</ul>
</li>
<li>Execute the call</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">time</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>sntp<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">on_time</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">seconds</span>:<span style="color:#bbb"> </span>/1<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">then</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">          </span>- <span style="color:#309;font-weight:bold">lambda</span>:<span style="color:#bbb"> </span>|-<span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#c30;font-style:italic">              LightCall status_led_call = id(status_led).turn_on();
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#c30;font-style:italic">              status_led_call.set_state(true);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#c30;font-style:italic">              status_led_call.set_transition_length(100);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#c30;font-style:italic">              if(id(mhz19_co2).state &gt;= 1500) {
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_rgb(0.392, 0.015, 0.588); // Purple
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_brightness(1.00);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#c30;font-style:italic">              } else  if(id(mhz19_co2).state &gt;= 1200) {
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_rgb(0.921, 0.000, 0.000); // Red
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_brightness(0.75);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#c30;font-style:italic">              } else  if(id(mhz19_co2).state &gt;= 900) {
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_rgb(0.620, 0.392, 0.000); // Orange
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_brightness(0.27);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#c30;font-style:italic">              } else if(id(mhz19_co2).state &gt;= 650) {
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_rgb(0.031, 0.690, 0.007); // Green
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_brightness(0.18);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#c30;font-style:italic">              } else {
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_rgb(0.007, 0.600, 0.749); // Blue
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#c30;font-style:italic">                status_led_call.set_brightness(0.15);
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#c30;font-style:italic">              }
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#c30;font-style:italic">              status_led_call.perform();</span><span style="color:#bbb">              
</span></span></span></code></pre></div><h2 id="results">
    Results 
    
    <a class="header-link" href="#results">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Flashed the compiled esphome firmware and was greeted by some wonderful logs:</p>
<pre tabindex="0"><code class="language-esphome" data-lang="esphome">[20:43:27][D][pmsx003:236]: Got PM1.0 Concentration: 3 µg/m^3, PM2.5 Concentration 4 µg/m^3, PM10.0 Concentration: 4 µg/m^3 
[20:43:28][D][mhz19:057]: MHZ19 Received CO₂=693ppm Temperature=24°C Status=0x00
</code></pre><p>The LEDs and OLED lighted up with the correct info:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00623.jpg" >
            <img alt="Sensor put on the wall" src="/monitoring-co2-levels-the-diy-way/images/dl_DSC00623_hud2ad050a9e977833f2f7d3565dda28c3_4228000_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I added the entities in HomeAssistant, left it for a while and got these wonderful graphs:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-12-30_23-03-22_539d435e-861a-4c78-997d-4546fb31184a.png" >
            <img alt="HomeAssistant CO2 sensor" src="/monitoring-co2-levels-the-diy-way/images/dl_chrome_2022-12-30_23-03-22_539d435e-861a-4c78-997d-4546fb31184a.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_chrome_2022-12-30_23-03-42_c3a65a71-6589-4fb0-831a-9650f13c1a51.png" >
            <img alt="HomeAssistant PM sensor" src="/monitoring-co2-levels-the-diy-way/images/dl_chrome_2022-12-30_23-03-42_c3a65a71-6589-4fb0-831a-9650f13c1a51.png" />
        </a>
        
    </figure>


Guess when I started soldering for another project 😅.</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Overall, I'm pleased with this project, I didn't take very long to make, it's not too big, it's not really distracting unless the level is too high. It's going to be a really useful tool for long programming or soldering sessions.</p>

 ]]></content:encoded></item><item><title>Adding an mSATA port to a Lenovo M73</title><description> &lt;p>Adding a mSATA port to the motherboard of a Lenovo ThinkCenter M73 to enable me to add High Availability to my homelab.&lt;/p></description><link>https://blog.thestaticturtle.fr/adding-an-msata-port-to-a-lenovo-m73/</link><guid>https://blog.thestaticturtle.fr/adding-an-msata-port-to-a-lenovo-m73/</guid><category> Homelab</category><category> Servers</category><category> Electronics</category><category> Diy</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 01 May 2023 02:02:02 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/adding-an-msata-port-to-a-lenovo-m73/images/cover_huf4e003067d8565f8f8df8bebca8a832b_275127_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/adding-an-msata-port-to-a-lenovo-m73/images/cover_huf4e003067d8565f8f8df8bebca8a832b_275127_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Adding an mSATA port to a Lenovo M73</h1>
<span class="subtitle"><p>Adding a mSATA port to the motherboard of a Lenovo ThinkCenter M73 to enable me to add High Availability to my homelab.</p></span>
<br>

    <img class="" src='/adding-an-msata-port-to-a-lenovo-m73/images/cover_huf4e003067d8565f8f8df8bebca8a832b_275127_1350x900_fit_q80_box.jpg' alt="Adding an mSATA port to a Lenovo M73"/>

<hr>

<p>The Lenovo M73p is part of the ThinkCenter line of business-oriented desktop computers designed, developed by Lenovo. ThinkCentre computers typically include mid-range to high-end processors, options for discrete graphics cards, and multi-monitor support.</p>
<p>Generally, business-oriented desktop computers have different sizes for each model. I'm particularly interested in the tiny ones.
They have been made popular by the <a target="_blank" href="https://www.servethehome.com/introducing-project-tinyminimicro-home-lab-revolution/">Tiny/Mini/Micro</a> project published on <a target="_blank" href="https://www.servethehome.com/">ServeTheHome</a>.</p>
<p>To summarize, the idea is that there are plenty of technologies where one would want several physical nodes. In my case, the end goal is a HA setup for Proxmox VE.</p>
<p>Aside from the fact that having multiple nodes is obviously better for HA, theses small servers usually don't use a lot of energy, which today is a pretty good thing 💰. My four M73p consume somewhere between 60-85 WH depending on the load. Way better than one server powerful which might not be enought for everything that will consume +150 WH and won't have any HA 😕.</p>
<p>They also can be found for very cheap, I bought my nodes for somewhere between €60 and €90 each.</p>
<p>Of course, they have some downside:</p>
<ul>
<li>Small, means that you don't fit a lot in there:
<ul>
<li>2.5” drive for older models, NVMe only for newer ones</li>
<li>Some models have a PCIe interface use for a dedicated GPU and. <a target="_blank" href="https://www.reddit.com/r/homelab/comments/sfkz8b/incredibly_happy_with_my_completed_sff_home_lab/">A user managed to fit a 10GbE network card</a>, but models that do have this interface are often 2x / 3x the price of what I paid for my nodes</li>
</ul>
</li>
<li>Limited upgradability:
<ul>
<li>The M73 has only two Mini-DIMM slots for ram</li>
<li>Only one SATA port</li>
<li>Some models (including the M73) have CPU sockets, <strong>but</strong> they don't accept every CPU, and some are locked</li>
</ul>
</li>
<li>Only one 1 GbE Ethernet.</li>
</ul>
<p>That being said, they are very capable machines, way more than I need for my homelab, especially if I have 4 of them.</p>
<p>In my homelab I'm planning to experiment with Ceph, or maybe GlusterFS which means data will be on the nodes themselves.</p>
<p>High Availability, however, requires at least two things that this computer lacks:</p>
<ul>
<li>Two disk  (one for the OS, on for the data), there is a trick to run Ceph on a disk partition, but I would like something “stable”</li>
<li>High-speed networking, for me, I don't think I require that much IOPS, So I think I'll go with 1Gbe for the proxmox cluster and 2.5Gbe for the Ceph cluster.</li>
</ul>
<p>In this article, I'll fix the two disk issue.</p>
<h2 id="options-for-multiple-disks">
    Options for multiple disks 
    
    <a class="header-link" href="#options-for-multiple-disks">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>There are multiple options that I considered, I wanted to leave the internal SATA port for a SSD for the data of Ceph cluster. That didn't leave many options for the OS:</p>
<ul>
<li>USB 2.0 Key bodged to an internal header</li>
<li>USB 2.0 SSD bodged to an internal header</li>
<li>USB 3.0 Key plugged in the back</li>
<li>USB 3.0 SSD Plugged in the back</li>
</ul>
<p>I canned the USB Key solution pretty quickly, unlike OSes like Unraid where there is barely anything written to disk, Proxmox writes quite a lot which would destroy the key very fast.</p>
<p>I also wanted to avoid using the USB3 port because I planned to use these (or at least one of these) for the Ceph network and didn't want to have to share the bandwidth.</p>
<p>The last solution was the USB 2.0 SSD bodged to an internal header, which would work, and I would have probably done it if I didn't notice the mSATA  port 🤔.</p>
<h2 id="msata">
    mSATA? 
    
    <a class="header-link" href="#msata">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>When you look at the M73 motherboard, you can see an obviously missing footprint marked MSATA1 right next to the Wi-Fi slot:

    <figure>
        <a target="_blank" href="images/dl_pn_2022-12-30_00-29-55_2b332c9f-56d0-4747-a1dd-108fa297efe8.png" >
            <img alt="M73 Motherboard M-SATA port" src="/adding-an-msata-port-to-a-lenovo-m73/images/_hud14100fd551410a435531e071c33cbce_1998900_ad50dffd362a860c9de51f87bb8d9493.webp" />
        </a>
        
    </figure>

</p>
<p>Which got me thinking. Could it be as simple as soldering a connector and plugging in an SSD?
Tried soldering one and of course it didn't work 😑, that would be too easy.</p>
<h2 id="adding-the-msata-slot">
    Adding the mSATA Slot 
    
    <a class="header-link" href="#adding-the-msata-slot">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After some research, I found that there are some M73 models that have a mSATA port, which makes this possible. So, I dug a little more.</p>
<p>I first <a target="_blank" href="https://www.youtube.com/watch?v=jEt6l2MREfc">found a video from someone who successfully modded the motherboard</a> and IT WORKED, I got really excited and dug even more. I then found what, I believe, is the source of this mod: <a target="_blank" href="https://kknews.cc/digital/earlzm4.html">https://kknews.cc/digital/earlzm4.html</a>. Turns out, all it needed was a few more passives and moving a resistor.</p>
<h3 id="parts-list">
    Parts list 
    
    <a class="header-link" href="#parts-list">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<table>
<thead>
<tr>
<th>Designation</th>
<th>Value</th>
<th>Package</th>
</tr>
</thead>
<tbody>
<tr>
<td>C1, C2, C3</td>
<td>0.1uF</td>
<td>0402</td>
</tr>
<tr>
<td>C4</td>
<td>220uF</td>
<td>6.3 mm</td>
</tr>
<tr>
<td>C5</td>
<td>330pF</td>
<td>0402</td>
</tr>
<tr>
<td>R1</td>
<td>8.2k</td>
<td>0402</td>
</tr>
<tr>
<td>R2,R3,R4,R5,R6,R7</td>
<td>0 Ohm</td>
<td>0402</td>
</tr>
<tr>
<td>Q1</td>
<td>2N7002/MOS-N</td>
<td>SOT-23</td>
</tr>
<tr>
<td>L1</td>
<td>1uH</td>
<td>0603</td>
</tr>
<tr>
<td>MSATA1</td>
<td>TE Connectivity 1775838-2</td>
<td>Mini PCIe, 52 Contacts, 0.8 mm, Receptacle, Surface Mount, 2 Rows</td>
</tr>
</tbody>
</table>
<h3 id="top-side">
    Top side 
    
    <a class="header-link" href="#top-side">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>On the top side, there are only three components. The inductor is pretty easy to solder, the cap, however, was a nightmare, I could not solder to that ground plane. The mSATA connector looks painful but with some solder paste, hot air, and a bit of rework with a fine point tip, flux, and solder wick, it's actually pretty easy

    <figure>
        <a target="_blank" href="images/dl_DSC00540.JPG" >
            <img alt="Motherboard top-side components." src="/adding-an-msata-port-to-a-lenovo-m73/images/dl_DSC00540_hu32f1c5da8b5fa029d825207987bd5250_492017_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h3 id="bottom-side">
    Bottom side 
    
    <a class="header-link" href="#bottom-side">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Bottom side is pretty easy, even for 0402, I replaced 0 Ohm resistor with wire bridges and swapped the resistor with some hot air. Not the prettiest job, but it should work.

    <figure>
        <a target="_blank" href="images/dl_DSC00558.jpg" >
            <img alt="Motherboard bottom-side components." src="/adding-an-msata-port-to-a-lenovo-m73/images/dl_DSC00558_hu64c4625a2232eb5e24317d78a8b3febf_413694_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="testing">
    Testing 
    
    <a class="header-link" href="#testing">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After everything was soldered, I put a 128 GB SSD in the new slot, plugged a VGA cable and power and quickly hopped in the BIOS

    <figure>
        <a target="_blank" href="images/dl_DSC00528.JPG" >
            <img alt="SSD Installed in the motherboard." src="/adding-an-msata-port-to-a-lenovo-m73/images/dl_DSC00528_hu9fbc52e1c9fc2d75708ce83fbc7eab03_909025_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_DSC00610.JPG" >
            <img alt="SSD detected in the BIOS" src="/adding-an-msata-port-to-a-lenovo-m73/images/dl_DSC00610_hu21bf4d8e37efc5137e2fef5f32f07fd9_1233838_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I made sure the SSD was detected AND IT WAS 🥳 and quickly powered it off again because I didn't have the cooler installed.</p>
<p>I then put everything back together and also took the opportunity to clean the cooler and changed the thermal paste.</p>
<p>Finished by installing proxmox and that was it:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-12-30_02-07-42_dabdd681-b815-404e-9059-4510d3acbda4.png" >
            <img alt="Disk list in proxmox" src="/adding-an-msata-port-to-a-lenovo-m73/images/dl_chrome_2022-12-30_02-07-42_dabdd681-b815-404e-9059-4510d3acbda4.png" />
        </a>
        
    </figure>

</p>
<p>Booted just fine, both disks are detected. To finish things off, I did some benchmarks:</p>
<pre tabindex="0"><code>root@pve4:~# hdparm -Tt /dev/sdb
/dev/sdb:
 Timing cached reads:   17150 MB in  1.99 seconds = 8597.18 MB/sec
 Timing buffered disk reads: 428 MB in  3.01 seconds = 142.02 MB/sec
</code></pre><pre tabindex="0"><code>root@pve4:~# dd if=/dev/urandom of=/tmp/output bs=16k count=10k; rm -f /tmp/output
10240+0 records in
10240+0 records out
167772160 bytes (168 MB, 160 MiB) copied, 1.17751 s, 142 MB/s
</code></pre><h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I'm very pleased with this mod 😀, seems a bit dodgy but if the soldering's good, there is no reason it will pose any issue. Moreover, ⁣<code>142.02 MB/s</code> read and <code>142 MB/s</code> write not bad at all, way better than what a USB 2 key will do 🤣.</p>
<p>Now I just have to do the same mod to the 3 other thinkcenter 😭.</p>

 ]]></content:encoded></item><item><title>Microphone tally lights</title><description> &lt;p>Creating a microphone holder with tally lights for a live show and integrating it to the MIDAS M32 mixing console&lt;/p></description><link>https://blog.thestaticturtle.fr/microphone-tally-lights/</link><guid>https://blog.thestaticturtle.fr/microphone-tally-lights/</guid><category> Concerts</category><category> Electronics</category><category> Audio</category><category> M32</category><category> Diy</category><category> Web</category><category> 3 d</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 13 Mar 2023 11:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/microphone-tally-lights/images/cover_hu3c2bcfc4185ad54c95676fb6184adedb_12827236_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/microphone-tally-lights/images/cover_hu3c2bcfc4185ad54c95676fb6184adedb_12827236_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Microphone tally lights</h1>
<span class="subtitle"><p>Creating a microphone holder with tally lights for a live show and integrating it to the MIDAS M32 mixing console</p></span>
<br>

    <img class="" src='/microphone-tally-lights/images/cover_hu3c2bcfc4185ad54c95676fb6184adedb_12827236_1350x900_fit_q80_box.jpg' alt="Microphone tally lights"/>

<hr>

<blockquote class="warning">
    
    
        <p class="warning-text">
This is a stupidly long article, it details the concept, design, and construction phases thoroughly, you'll need probably more than 45 min to really read it
</p>
    
</blockquote>
<p>Every once in a while, I work in my local choir. Every few years, they do a tour singing music for around 3h. This year there are more than 25 singers and 15 microphones. Add microphones for the drums, bass, guitar, saxophone, trumpet, ..... and you reach the maximum 32 channels of our MIDAS M32 console quite fast.</p>
<p>You may notice that 25 is greater than 15 🤔, that's because we have one or two techs who hand out the mics according to the all mighty “mic-sheet”.</p>
<p>Basically, this sheet is a grid of every song and every microphone with the performer's name if they need one. It gets fine-tuned over time during rehearsals so that when the first real concert comes, the mic transitions between performers are as smooth as possible. This is fine and worked for over a decade, but we are still human and sometimes mistakes happen. For example, someone might grab the wrong mic and go on stage, or maybe the microphone has become magically invisible 👻.</p>
<p>Combine that with my love for problem-solving 🤓, and I decided that I would do a system that can do multiple things:</p>
<ul>
<li>Keep the microphones neatly organized</li>
<li>Connect to the MIDAS M32 and retrieve mute &amp; fader values</li>
<li>Display mute status to the backstage techs</li>
<li>Determine if a microphone is in the holder or in someone's hand</li>
<li>Display this in the mixing booth so that we can figure out what the hell is going on if something is wrong</li>
</ul>
<p>One problem that could still happen is that someone could put the wrong microphone in the wrong holder, we would have no way of knowing which one is the right one. But my guess is that it's more likely that the mics run out of battery than someone misreading two numbers (one on the mic and one on the holder) 🤷‍♂️.</p>
<h2 id="microphone-holders">
    Microphone holders 
    
    <a class="header-link" href="#microphone-holders">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I thought about using RFID tags on each mic to solve the &quot;wrong holder&quot; issue, and that would indeed make sure that the right microphone is in the proper holder. But, that brings me to the 2nd thing that this project needs to be: <strong>cheap!</strong> 💰 The thing is that I'm doing everything myself and don't want to spend 20 EUR per holder, especially knowing that one could break in the chaos of the backstage. No, I want it to be as cheap as possible.</p>
<p>But, I still need to have 15 of them connected to a controller capable of talking to the M32.</p>
<p>That took some though 🤔 as I originally wanted to do use a single bus or a daisy-chaining approach, where all holders would have an IN and an OUT port to simplify wiring.</p>
<h3 id="connecting-15-holders-together">
    Connecting 15 holders together 
    
    <a class="header-link" href="#connecting-15-holders-together">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Single bus/daisy-chaining for the LEDs is easy, a few WS2812B in each holder will do just fine, and they already work with daisy-chaining ✅.</p>
<p>The input for the microphone detection was a bit trickier though, I first looked at one wire IO extenders like the <a target="_blank" href="https://www.analog.com/media/en/technical-documentation/data-sheets/DS2408.pdf">DS2408</a> or the <a target="_blank" href="https://www.analog.com/media/en/technical-documentation/data-sheets/DS2413.pdf">DS2413</a>, but they are respectively 8 EUR and 3 EUR for a single chip in the quantities I need. Far too expensive 💸.
At the same time I started this project, I had an ongoing PCB order, I thought that I could potentially put a full RP2040 block in each holder, but that would also be 2 - 5 EUR per holder in the quantities I require.</p>
<p>So short of doing some analog trickery to make the thing work on one wire 😐, I said f- that and committed to a star topology. That means I have to bring a cable from the controller to each holder that contains these signals:</p>
<ul>
<li>Ground</li>
<li>LED In</li>
<li>+5V</li>
<li>LED Out</li>
<li>Microphone detection OUT</li>
</ul>
<p>The LED out of each holder is wired to the LED in of the next one, and the &quot;Microphone detection&quot; button goes to a GPIO of the controller.</p>
<h3 id="detecting-the-microphone">
    Detecting the microphone 
    
    <a class="header-link" href="#detecting-the-microphone">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The first thing anyone would think of is a simple switch. While that could work, being on a budget also means that we don't have the same microphone, some are smaller than others, and I wanted to avoid doing two weeks of cad to try to make a holder compatible with everything (especially since I didn't even have access to the microphones at the start).</p>
<p>The second option I thought of 💡 is an IR proximity sensor. Those are also pretty cheap, <a target="_blank" href="https://www.amazon.fr/dp/B08DR1W3BK">15eur for a pack of twenty</a> is pretty good. So, I went ahead and ordered a pack, I could always find a use for it elsewhere if it didn't work out.</p>
<p>Once I got them I discovered a fatal flaw in my plan, the microphones are black and round ⚫ which doesn't really reflect the light from the sensor very well if at all. After some thinking and even trying to go back to the switch option, I realized the detection could be &quot;normally closed&quot; and use the microphone to block the path of the IR beam instead of reflecting it.</p>
<p>Perfect, I now have a cheap way of determining whether the microphone is in the holder or not 👍.</p>
<h3 id="cad">
    CAD 
    
    <a class="header-link" href="#cad">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I still needed an actual solid piece of plastic to hold the microphone. The holder needs to satisfy these requirements:</p>
<ul>
<li>Holding a wide variety of microphones</li>
<li>Able to be screwed to a piece of wood</li>
<li>Able to hold a label for the microphone number</li>
<li>A way to display and diffuse the LED light</li>
<li>Enought space for IR LEDs in the holder arms</li>
<li>Flexible enought to accommodate even bigger microphones if necessary</li>
</ul>
<p>After some revisions (some of which happened while I was already printing) 🤫, I ended up with this design which is printed upright in two parts (holder and led diffuser):


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2023-02-27_17-01-34_1a44898c-4a17-4f2d-9c47-e3b53fc68dc0.png">
                            <img src="/microphone-tally-lights/images/dl_SLDWORKS_2023-02-27_17-01-34_1a44898c-4a17-4f2d-9c47-e3b53fc68dc0.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2023-02-27_17-02-01_bbebc3dd-72b8-4af6-bad8-e9b331f6c86a.png">
                            <img src="/microphone-tally-lights/images/dl_SLDWORKS_2023-02-27_17-02-01_bbebc3dd-72b8-4af6-bad8-e9b331f6c86a.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div></p>
<p>The LEDs are inserted in the sidearms, in retrospect, it would probably have been better to order modules with 3 mm LEDs instead of 5 mm ones since they stick out a bit, but it doesn't need to be perfect, it just needs to work. It's going to get beat up anyway 🙄. The cable for the LEDs get routed in a small channel in the arm, which then comes out in the back, where I can put the rest of the components.

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2023-02-27_17-04-21_9da479a1-a599-4488-a3d5-e359dd1ae75a.png" >
            <img alt="Microphone holder clip" src="/microphone-tally-lights/images/dl_SLDWORKS_2023-02-27_17-04-21_9da479a1-a599-4488-a3d5-e359dd1ae75a.png" />
        </a>
        
    </figure>


Once the LEDs are inserted, a small piece of heat-shrink with a hole is placed over the LED to secure it in place:

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-05-154217_002_crop.jpeg" >
            <img alt="" src="/microphone-tally-lights/images/dl_signal-2023-03-05-154217_002_crop.jpeg" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2023-02-27_17-03-31_94090686-b99d-4937-a28f-a372fcfec0a8.png" >
            <img alt="Microphone holder LED channels" src="/microphone-tally-lights/images/dl_SLDWORKS_2023-02-27_17-03-31_94090686-b99d-4937-a28f-a372fcfec0a8.png" />
        </a>
        
    </figure>

</p>
<p>To make it easier to build the 16 modules, I chose to use a classic LED strip, I cut out 6 LEDs and jammed them in a little recess that I put in the design.</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2023-02-27_23-27-26_547684df-6f3d-46d3-8c26-a26b1bf7cacf.png">
                            <img src="/microphone-tally-lights/images/dl_SLDWORKS_2023-02-27_23-27-26_547684df-6f3d-46d3-8c26-a26b1bf7cacf.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-05-152528_028_cropped.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-05-152528_028_cropped.jpeg" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>Finally, the PCB of the IR module gets attached with a piece of double-sided tape on the back and everything gets soldered together.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-05-152528_027_cropped.jpeg" >
            <img alt="" src="/microphone-tally-lights/images/dl_signal-2023-03-05-152528_027_cropped_hud1eb2dbd28346db584d0cc50e38aa13d_178437_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I then spent 4 days printing everything (I printed 4 modules at the same time, which kept the printer busy for around 18h 😴). The assembly was actually straightforward once I got the hang of it, but I'm glad it's done.</p>
<p>Now that everything is assembled, I printed some LED diffusers and stuck some labels with the microphone number printed on it:

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-05-152528_008_eddited.jpeg" >
            <img alt="" src="/microphone-tally-lights/images/dl_signal-2023-03-05-152528_008_eddited_hu2e9ecdeba47b0fc1e53ee1d6c3449f5c_171018_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h4 id="flaws-and-improvement">
    Flaws and improvement 
    
    <a class="header-link" href="#flaws-and-improvement">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>If I were to re-design this, I would probably do the following:</p>
<ul>
<li>Manufacture two PCBs:
<ul>
<li>One that follows the shape of the arms with side mounted IR LEDs and photodiode,</li>
<li>One that I could put on the back with some side mounted <a target="_blank" href="https://www.lcsc.com/product-detail/Light-Emitting-Diodes-LED_Worldsemi-WS2812B-4020_C965557.html">WS2812B-4020</a> instead of an LED strip. In addition, use some sort of interconnect with the side arms PCB,</li>
</ul>
</li>
<li>Maybe print the holder out of a semi-flexible material and not PLA (at least for the arms). Because while I did print them at 100% infill, I fully expect at least one to break at some point. A hard TPU seems like a better bet. Unfortunately, I don't think my printer will handle it well, especially in the winter.</li>
</ul>
<p>This would make the holder slimmer and sturdier. It might even be possible to print this hypothetical version in metal.</p>
<h3 id="wiring">
    Wiring 
    
    <a class="header-link" href="#wiring">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Originally, I wanted to have a cable going out the bottom of each holder with the JST connector and the same cable going to the controller. That would have resulted in a big mess of cables, especially at the start 😵.</p>
<p>Once everything was printed, we began shopping for a piece of wood that could holder everything. By pure luck, we found a <a target="_blank" href="https://www.leroymerlin.fr/produits/menuiserie/panneau-bois-tablette-etagere-tasseau-moulure-et-plinthe/moulure-champlat-baguette-angle/nez-de-cloison-medium-mdf-pour-cloison-de-70-mm-11-x-73-mm-l-2-5-m-70180530.html">U-Shaped channel of MDF</a> that would be:</p>
<ul>
<li>Tall enough for the holders,</li>
<li>Long enough for 16 of them,</li>
<li>Deep enough to route the cable behind.</li>
</ul>
<p>
    <figure>
        <a target="_blank" href="images/dl_3b273619-b4d4-4662-92de-be146b45277a.webp" >
            <img alt="U-Shaped channel of MDF" src="/microphone-tally-lights/images/dl_3b273619-b4d4-4662-92de-be146b45277a.webp" />
        </a>
        
    </figure>

</p>
<p>So, my dad and I started by drilling the two screw holes for each holder, plus a 20 mm hole for the cable to pass to the back. We choose to leave 2.5 cm of space between each holder to leave enough room to grab the mic easily, and something like 15 cm of space on each side for the controller.</p>
<p>Unfortunately, the +2 m piece of MDF wasn't very stiff, and the thing was flapping like crazy 🤔. I'll come back to that. To pile up on the bad news, the screws we chose were a bit too long by something like 5 mm.</p>
<p>Fortunately, we found some aluminum channels that are 20 mm deep and the perfect width. These aluminum extrusions are typically used on the side of flight cases. Mine looks a bit like this one:
<a target="_blank" href="https://www.thomann.de/fr/adam_hall_6102_schliessprofil.htm">https://www.thomann.de/fr/adam_hall_6102_schliessprofil.htm</a>

    <figure>
        <a target="_blank" href="images/dl_9185483.jpg" >
            <img alt="" src="/microphone-tally-lights/images/dl_9185483_hu4dcbb7a645a86b6ca306acebb40051ba_291479_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>It was perfect 😄. The board looks super nice, the screws are not too long any more and as the bonus, the board is super stiff now:

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-01-164250_009.jpeg" >
            <img alt="First test of all the holders on the board" src="/microphone-tally-lights/images/dl_signal-2023-03-01-164250_009_hue9ee0d582077a8dcc56e56edc8682f89_165073_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>You can see that the bottom screw is barely short enough, but it will actually be very helpful later to hold the cable harness 😏:

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-01-164250_008.jpeg" >
            <img alt="Backside of the board" src="/microphone-tally-lights/images/dl_signal-2023-03-01-164250_008_hu2963915986f2e0d651dc96e8cdcc26e4_220477_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I then proceeded by cutting a ton of wires for power distribution and for the addressable LEDs data line 😫. Instead of soldering everything manually, I choose to use these wonderful heat shrink pieces that already have some solder in them: <a target="_blank" href="https://fr.aliexpress.com/item/1005003878417358.html">https://fr.aliexpress.com/item/1005003878417358.html</a>.

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-01-164250_004.jpeg" >
            <img alt="LED Cable harness heat shrink pieces" src="/microphone-tally-lights/images/dl_signal-2023-03-01-164250_004_hucac9e6a995edef2dda3686b46ff8d9bd_160206_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>The white cable you can see is the connection of the IR module. To connect this one, I cut a 16 conductor by +2 m long piece of ribbon cable, separated the cable where required and soldered the same heat shrink pieces everywhere.</p>
<p>This made for a very nice cable harness to put in the back (The photo is missing the ribbon cable)  👍.

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-01-164250_003.jpeg" >
            <img alt="LED Cable harness" src="/microphone-tally-lights/images/dl_signal-2023-03-01-164250_003_hu3551b6a211be09278b69d14a74b7c9a7_178004_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>To fix everything in place, I printed some big washer that I placed on the screw post of the holder with the cable underneath. After that was done, everything was nice and neat:

    <figure>
        <a target="_blank" href="images/dl_signal-2023-03-01-164250_011.jpeg" >
            <img alt="" src="/microphone-tally-lights/images/dl_signal-2023-03-01-164250_011_hu6ffff83009ff508cb2a7b4cab50af887_860865_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="controller">
    Controller 
    
    <a class="header-link" href="#controller">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that I have 15 holders and a spare, I need something to control them. Originally, I wanted to use an ESP32 and fully integrate everything.</p>
<p>The controller needs to be able to do a few basic things:</p>
<ul>
<li>Connect to the M32 OSC protocol and get mutes/fader values,</li>
<li>Drive the WS2812B,</li>
<li>Read all the 15+1 inputs.</li>
</ul>
<p>Alongside that, a few other things need to be considered when being in a &quot;pro&quot; context:</p>
<ul>
<li>Stability: The thing should start fast, can withstand a power cut, handle potential network disconnects, etc.</li>
<li>Reliability: The thing has to stay connected and work for hours without interruption.</li>
</ul>
<p>That meant Wi-Fi was out of the question because there are too many issues when numerous people are connected, and I don't have the time/budget to set up a proper Wi-Fi network 📡.</p>
<p>Fortunately, we already have a wired network planned, which means: let's go with Ethernet.</p>
<p>I actually figured out quickly how to get Ethernet working on the ESP32. While I did make some progress fairly fast, I figured that I couldn't reach the required stability and reliability level by the start of the first concert. It's just too much work to re-implement everything manually for a quick project.</p>
<p>So, I dug out a now very rare PI Zero 2, and decided that I was going to use it 🤷‍♂️.</p>
<h3 id="reading-inputs">
    Reading inputs 
    
    <a class="header-link" href="#reading-inputs">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>At first, I thought of a whole plan to use IO extender to read all the inputs. Then I looked closely at the PI and I realized that the thing has GPIO, I never used them to the point where I just forgot that they existed 😓.</p>
<p>A minimal issue is that my signal from the holders are +5V, and I need them to be ≤3.3V. This can be taken care of with a resistor divider, I chose 10K/10K because that's all I had in hand, which gave me 2.5V, not quite 3.3V but still above the threshold, perfect 👍.</p>
<h3 id="leds">
    LEDs 
    
    <a class="header-link" href="#leds">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Well, there is not a lot to say here. The ws2812b is a single pin, so I just used one that was available and called it a day 😅</p>
<h3 id="ethernet">
    Ethernet 
    
    <a class="header-link" href="#ethernet">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I have some cheap Ethernet dongles. I cut one up and soldered it up to the D+/D- test pads of the PI Zero, that gives me Ethernet connectivity</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_Raspberry-Pi-Zero-2-W-Test-Pad.png" >
            <img alt="PI0 2W Test pads description" src="/microphone-tally-lights/images/dl_Raspberry-Pi-Zero-2-W-Test-Pad_hu5266184083fef1590513415e36912822_146024_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h3 id="wiring-1">
    Wiring 
    
    <a class="header-link" href="#wiring-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>To make things easier, I decided that I would use a PCB. The issue is that, again, that I needed this thing as soon as possible ⏱, so I can't do a pre-made PCB and wait a week for shipping. I decided that I would, instead, use a perfboard and do some manual soldering.</p>
<p>There really isn't a lot to say, I soldered in some headers, the resistors for the dividers and some terminal blocks for power and the LEDs data.</p>
<p>I also soldered some DuPont connectors to the D+ and D- of the Raspberry Pi for the Ethernet card instead of using a USB to OTG adapter.</p>
<h3 id="cad-1">
    CAD 
    
    <a class="header-link" href="#cad-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I spent some time making the PCB and the Raspberry Pi fit inside a very basic box

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2023-03-03_14-17-44_c818f447-b903-49a9-8e36-a8189312d429.png" >
            <img alt="Controller box" src="/microphone-tally-lights/images/_hueb9670dce5cb97ea122cbf672bec6982_74428_993047ad98153baa2e06eb2b07471423.webp" />
        </a>
        
    </figure>

</p>
<p>Added some holes for power, and status LEDs:

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2023-03-03_14-19-21_2e9fe2e9-38e9-4a9c-ab72-a7aa6c88dec7.png" >
            <img alt="Controller box with LEDs and power holes" src="/microphone-tally-lights/images/_hue94c3fc9601cce5e6dd47c35f8585f03_72685_9e1915428dae022aa8325068065702a8.webp" />
        </a>
        
    </figure>

</p>
<p>I then added a place for the network card

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2023-03-03_14-19-44_7d8352f2-927a-42f0-b1e2-a8b90358382d.png" >
            <img alt="Controller with the network card slot" src="/microphone-tally-lights/images/_hu5abfad9b2bb4e39e6eca756fed30aabc_117136_59363e7b635d9c2dcd97cc6056a54bfa.webp" />
        </a>
        
    </figure>

</p>
<p>Once that got printed, I put the PCB, network card (which I had to hot glue in to secure it), LEDs and the power connector in it. And, finally, wired the 16 inputs from the wire harness and screwed in the power cables and LED data cable.


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-13-224316_006.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-13-224316_006_hude1d629a4d7b5b32c3ea1d31489ffafa_247039_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-13-224316_004.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-13-224316_004_hu790c2da0ddb0584d0c0badc13ef4c0ee_196445_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div></p>
<p>After securing it on the MDF board, it looked perfect.


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-13-224316_010.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-13-224316_010_hu6d631e1248670ad80dcf4079af02dd1e_212878_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-13-224316_009.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-13-224316_009_hu1e8c013703fddac8d1e8dc1506ea9089_122981_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div></p>
<h2 id="software">
    Software 
    
    <a class="header-link" href="#software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I needed it to work as soon as possible 🐇, I went with something I knew for sure and used python 🐍.</p>
<p>The first thing I did was test out the LEDs and inputs. I used the circuit python libraries because it just works. Didn't have to fiddle to make something work correctly. And, sure enough, everything worked right away, nice 👍.</p>
<p>Initially, I did everything in one big script, but that became an issue when I had multiple loops and a web server.</p>
<p>I then thought that it might be better / easier to write a few interconnected modules than to write a big one 🤔. There are a few options for connecting multiple parts together. ZMQ and MQTT are pretty popular, I then remembered that MQTT has a web socket version (which I could easily use for a Web UI later 🌐) and went with it.</p>
<h3 id="mqtt-server">
    MQTT Server 
    
    <a class="header-link" href="#mqtt-server">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I chose to use the mosquito mqtt server, installing it was pretty straightforward:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>sudo apt update
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>sudo apt install -y mosquitto mosquitto-clients
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>sudo systemctl <span style="color:#366">enable</span> mosquitto.service
</span></span></code></pre></div><p>Then, I just need to enable anonymous access, traditional MQTT and Websocket support in the config:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-conf" data-lang="conf"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#033">listener</span> <span style="color:#033">1883</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#033">listener</span> <span style="color:#033">8080</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#033">protocol</span> <span style="color:#033">websockets</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#033">allow_anonymous</span> <span style="color:#069;font-weight:bold">true</span>
</span></span></code></pre></div><p><em>Note: using authentication here wouldn't be very useful, since the credentials could have been easily recovered in the source code of the webui</em> 🤷‍♂️</p>
<p>After a quick test with the MQTT explorer app on my computer to check that it was working. So, I went ahead and started working on the global config.</p>
<h3 id="configuration">
    Configuration 
    
    <a class="header-link" href="#configuration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The <code>config.py</code> file of x32_tally contains several variables that I can configure to alter the way the tool works. This includes:</p>
<ul>
<li>The address of the X32,</li>
<li>The address of the MQTT server,</li>
<li>Log levels for every module,</li>
<li>Colors for the tally lights:
<ul>
<li>Muted / Active,</li>
<li>Muted blinking ON / Active blinking ON,</li>
<li>Muted blinking OFF / Active blinking OFF.</li>
</ul>
</li>
<li>Pin for the neopixels,</li>
<li>List of input channels with:
<ul>
<li>The status (enabled or not),</li>
<li>The list of LEDs for the tally lights,</li>
<li>The pin for the &quot;on-stand&quot; detection.</li>
</ul>
</li>
</ul>
<h3 id="io-configuration">
    IO Configuration 
    
    <a class="header-link" href="#io-configuration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The <code>io.py</code> file contains definitions for the <code>LedController</code> and <code>InputController</code> classes. These act as a wrapper for the Adafruit libraries, this allows me/someone to easily change the input/output method without the need to rewrite every module 😏.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">class</span> <span style="color:#0a8;font-weight:bold">LedController</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#069;font-weight:bold">def</span> __init__(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>		highest_pixel_id <span style="color:#555">=</span> <span style="color:#f60">0</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>		<span style="color:#069;font-weight:bold">for</span> ch_n, ch <span style="color:#000;font-weight:bold">in</span> config<span style="color:#555">.</span>input_channels<span style="color:#555">.</span>items():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>			<span style="color:#069;font-weight:bold">if</span> <span style="color:#c30">&#34;tally_leds&#34;</span> <span style="color:#000;font-weight:bold">in</span> ch:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>				highest_pixel_id <span style="color:#555">=</span> <span style="color:#366">max</span>(highest_pixel_id, <span style="color:#555">*</span>ch[<span style="color:#c30">&#34;tally_leds&#34;</span>])
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>		self<span style="color:#555">.</span>pixels <span style="color:#555">=</span> neopixel<span style="color:#555">.</span>NeoPixel(config<span style="color:#555">.</span>tally_neopixel_pin, highest_pixel_id <span style="color:#555">+</span> <span style="color:#f60">1</span>, auto_write<span style="color:#555">=</span><span style="color:#069;font-weight:bold">False</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">update</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>		self<span style="color:#555">.</span>pixels<span style="color:#555">.</span>show()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">set</span>(self, leds, r, g, b):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>		<span style="color:#069;font-weight:bold">for</span> led <span style="color:#000;font-weight:bold">in</span> leds:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>			self<span style="color:#555">.</span>pixels[led] <span style="color:#555">=</span> (r, g, b)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">class</span> <span style="color:#0a8;font-weight:bold">InputController</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>	<span style="color:#069;font-weight:bold">def</span> __init__(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>		self<span style="color:#555">.</span>buttons <span style="color:#555">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">get</span>(self, pin):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>		<span style="color:#069;font-weight:bold">if</span> pin<span style="color:#555">.</span>id <span style="color:#000;font-weight:bold">not</span> <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>buttons:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>			self<span style="color:#555">.</span>buttons[pin<span style="color:#555">.</span>id] <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>DigitalInOut(pin)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span>			self<span style="color:#555">.</span>buttons[pin<span style="color:#555">.</span>id]<span style="color:#555">.</span>direction <span style="color:#555">=</span> digitalio<span style="color:#555">.</span>Direction<span style="color:#555">.</span>INPUT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span>		<span style="color:#069;font-weight:bold">return</span> self<span style="color:#555">.</span>buttons[pin<span style="color:#555">.</span>id]<span style="color:#555">.</span>value
</span></span></code></pre></div><p>This file also contains a function for creating MQTT clients. It takes a name and returns the client:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">get_mqtt_client</span>(client_id):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>	client <span style="color:#555">=</span> mqtt<span style="color:#555">.</span>Client(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>		client_id<span style="color:#555">=</span>client_id,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>		reconnect_on_failure<span style="color:#555">=</span><span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>	)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>	client<span style="color:#555">.</span>enable_logger(logging<span style="color:#555">.</span>getLogger(<span style="color:#c30">&#34;MQTT&#34;</span>))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span>	client<span style="color:#555">.</span>connect(config<span style="color:#555">.</span>mqtt[<span style="color:#c30">&#34;host&#34;</span>], config<span style="color:#555">.</span>mqtt[<span style="color:#c30">&#34;port&#34;</span>], <span style="color:#f60">60</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span>	<span style="color:#069;font-weight:bold">return</span> client
</span></span></code></pre></div><p>Finally, this module also broadcasts the input channel config every time it's loaded. This is only for the Web UI, as it obviously doesn't have access to the <code>config.py</code> file.</p>
<h3 id="1st-module-midas-m32-to-mqtt-bridge">
    1st module: MIDAS M32 to MQTT bridge 
    
    <a class="header-link" href="#1st-module-midas-m32-to-mqtt-bridge">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As the headline suggests, the most important thing is to forward messages from the M32 to the MQTT server so that other modules can access it.</p>
<p>As a request from the sound engineer, <strong>I purposefully did not implement the MQTT to M32 side to avoid something writing to the console and causing something bad 😕</strong> (That being said, it would be trivial to implement).</p>
<p>The MIDAS M32 uses a custom implementation of the <a target="_blank" href="https://ccrma.stanford.edu/groups/osc/index.html">OSC</a> protocol. OSC is wonderful, works super well, and it's widely used. It's no wonder they used it.</p>
<p>One tiny annoying thing about their implementation is that you need to send packets to the port <code>10023</code> of the console. But it does not respond to you on <code>10023</code>, instead it responds to whatever ephemeral port the system decided to use to send the packet. This means that you need to keep the same socket.</p>
<p>It's a small inconvenience, but it also means that you don't have to select a different port for each app 👍.</p>
<p>Somebody did an incredible job of reverse engineering what each command does and published a <a target="_blank" href="https://sites.google.com/site/patrickmaillot/x32#h.p_rE4IH0Luimc0">ton of software for the X32</a>. Including an emulator, which is helpful when you don't have the 3600 EUR console next to you. He also published a <a target="_blank" href="https://drive.google.com/file/d/1Snbwx3m6us6L1qeP1_pD6s8hbJpIpD0a/view">spec sheet</a> of OSC commands which is very useful.</p>
<p>Turns out that to query a setting of the console you just have to send the same command that you would normally send for setting it but without the parameters.</p>
<p>Essentially, that would mean that I need to send <code>/ch/XX/mix/on</code> and <code>/ch/XX/mix/fader</code> every few hundreds of a second to poll the status. That would be quite resource intensive for both devices 😓.</p>
<p>Instead, the engineers over at Behringer/Midas added the <code>/xremote</code> command, this command will subscribe you to every update happening                                                                     on the console (except VU meters for which there is a special command). The only thing is that you need to resubscribe every few seconds to keep receiving updates 👍.</p>
<p>To recap, I send:</p>
<ul>
<li>Every few seconds (read 5):
<ul>
<li>The <code>/xremote</code>  command to subscribe to console updates.</li>
</ul>
</li>
<li>Every 60sec to force update of the values as a sanity check:
<ul>
<li>The <code>/ch/XX/mix/on</code>  query to get the mute status of the channel XX,</li>
<li>The <code>/ch/XX/mix/fader</code> query to get the fader value of the channel XX,</li>
<li>The <code>/ch/XX/config/icon</code> query to get the icon of the channel XX,</li>
<li>The <code>/ch/XX/config/name</code>  query to get the name of the channel XX,</li>
<li>The <code>/ch/XX/config/color</code> query to get the color of the channel XX.</li>
</ul>
</li>
</ul>
<p>Unfortunately, after some testing with the real thing (as I started the development with an emulator), it turns out that the <code>/xremote</code> command doesn't send every fader update when changing cues, and it meant I had to wait for the forced update to refresh the status 😕.</p>
<p>To solve this, I had to dive into how the <code>/formatsubscribe</code> command works. This command allows the reception of regular updates for a topic. In this case, I subscribed to mute and fader values.</p>
<p>Note that this command did not work on the emulator, hence why I didn't use it from the start 🤷‍♂️.</p>
<p>On a tangent, I also looked at the <code>/showdump</code>, <code>/-prefs/show_control</code>, <code>/‐show/prepos/current</code> and <code>/-show/showfile/show/name</code> commands that allow me to get the show, its cues and the current position of cues.</p>
<p>So, how does that work in code. I used the <code>pythonosc</code> module to parse and build the OSC messages. I then wrote a class that inherit from <code>threading.Thread</code> to keep receiving the messages in the background. This is probably not necessary, but I wanted to be able to re-use this lib somewhere else if needed. I started by creating a few helper functions.</p>
<h4 id="sending-data">
    Sending data 
    
    <a class="header-link" href="#sending-data">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>I first wrote a &quot;send&quot; function that wouldn't make the module crash if, for some reason, the network was not working.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#09f;font-style:italic"># Wrapper function use to send data to the console.</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#09f;font-style:italic"># The function tries to send the message but does not raise an error if it fails (it just logs it).</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    <span style="color:#09f;font-style:italic"># This can happen if the network card is not up yet</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_send</span>(self, data: <span style="color:#366">bytes</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        <span style="color:#069;font-weight:bold">try</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            self<span style="color:#555">.</span>_socket<span style="color:#555">.</span>sendto(data, self<span style="color:#555">.</span>_address)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">OSError</span> <span style="color:#069;font-weight:bold">as</span> e:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            self<span style="color:#555">.</span>_log<span style="color:#555">.</span>warning(<span style="color:#c30">f</span><span style="color:#c30">&#34;Tried to send data but got: </span><span style="color:#a00">{</span>e<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        <span style="color:#069;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">Exception</span> <span style="color:#069;font-weight:bold">as</span> e:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            self<span style="color:#555">.</span>_log<span style="color:#555">.</span>error(<span style="color:#c30">f</span><span style="color:#c30">&#34;Tried to send data but got: </span><span style="color:#a00">{</span>e<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>)
</span></span></code></pre></div><p>Next, I wrote wrappers around the OSC commands of the x32 that I'll often use.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_x32_format_subscribe</span>(self, alias, addresses, start_i, end_i, interval<span style="color:#555">=</span><span style="color:#f60">20</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>        message <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">&#34;/formatsubscribe&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        message<span style="color:#555">.</span>add_arg(alias)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        <span style="color:#069;font-weight:bold">for</span> address <span style="color:#000;font-weight:bold">in</span> addresses:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            message<span style="color:#555">.</span>add_arg(address)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        message<span style="color:#555">.</span>add_arg(start_i)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        message<span style="color:#555">.</span>add_arg(end_i)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        message<span style="color:#555">.</span>add_arg(interval)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        self<span style="color:#555">.</span>_send(message<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_x32_renew</span>(self, alias):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>        message <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">&#34;/renew&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>        message<span style="color:#555">.</span>add_arg(alias)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        self<span style="color:#555">.</span>_send(message<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_x32_xremote</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        message <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">&#34;/xremote&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        self<span style="color:#555">.</span>_send(message<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_x32_info</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>        message <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">&#34;/info&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>        self<span style="color:#555">.</span>_send(message<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_x32_showdump</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>        message <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">&#34;/showdump&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>        self<span style="color:#555">.</span>_send(message<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span></code></pre></div><p>Followed that with a function to force query the status of the fader and to dump the whole show.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_query_channel</span>(self, channel):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>channel<span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/mix/on&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>channel<span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/mix/fader&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>channel<span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/config/icon&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>channel<span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/config/name&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>channel<span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/config/color&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_query_show</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        self<span style="color:#555">.</span>_x32_showdump()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/-prefs/show_control&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/‐show/prepos/current&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>        self<span style="color:#555">.</span>_send(OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/-show/showfile/show/name&#34;</span>)<span style="color:#555">.</span>build()<span style="color:#555">.</span>dgram)
</span></span></code></pre></div><p>Next, I did the re-sync and resubscribe functions. These functions are called respectively every 60 sec and every 5 sec.</p>
<p>The goal of the <code>_re_sync</code> is to be damn sure that our information is sync with the console. It also forces a <code>/formatsubscribe</code> command instead of a <code>/renew</code>.</p>
<p>The goal of the <code>_re_subscribe</code> command is to send the <code>/renew</code>, <code>/xremote</code> and <code>/info</code> commands to make sure we keep the update coming.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#09f;font-style:italic"># Resync function. This function is executed every 60sec to make sure the internal status is up-to-date with the console</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_re_sync</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        self<span style="color:#555">.</span>_log<span style="color:#555">.</span>info(<span style="color:#c30">&#34;[TX] Forced queried infos and subscribed&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        self<span style="color:#555">.</span>last_resync <span style="color:#555">=</span> time<span style="color:#555">.</span>time()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        <span style="color:#069;font-weight:bold">for</span> i <span style="color:#000;font-weight:bold">in</span> <span style="color:#366">range</span>(<span style="color:#f60">1</span>, <span style="color:#f60">33</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            self<span style="color:#555">.</span>_query_channel(i)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        self<span style="color:#555">.</span>_x32_format_subscribe(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            alias<span style="color:#555">=</span><span style="color:#c30">&#34;/subscribed/faders&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            addresses<span style="color:#555">=</span>[<span style="color:#c30">&#34;/ch/**/mix/on&#34;</span>, <span style="color:#c30">&#34;/ch/**/mix/fader&#34;</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            start_i<span style="color:#555">=</span><span style="color:#f60">1</span>, end_i<span style="color:#555">=</span><span style="color:#f60">32</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>            interval<span style="color:#555">=</span><span style="color:#f60">20</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>        self<span style="color:#555">.</span>_query_show()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>    <span style="color:#09f;font-style:italic"># Resubscribe function. This function is executed every 5sec to subscribe to updates from the console</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_re_subscribe</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>        self<span style="color:#555">.</span>last_resubscribe <span style="color:#555">=</span> time<span style="color:#555">.</span>time()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>        self<span style="color:#555">.</span>_log<span style="color:#555">.</span>info(<span style="color:#c30">&#34;[TX] Renewed subscriptions&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>        self<span style="color:#555">.</span>_x32_xremote()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>        self<span style="color:#555">.</span>_x32_info()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>        self<span style="color:#555">.</span>_x32_renew(<span style="color:#c30">&#34;/subscribed/faders&#34;</span>)
</span></span></code></pre></div><h4 id="receiving-data--handlers">
    Receiving data &amp; handlers 
    
    <a class="header-link" href="#receiving-data--handlers">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Now that we can send messages, we need to receive them. I started by writing a handler for all messages. This function receives an OSC message and forward it to the appropriate internal or external handler.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#09f;font-style:italic"># Internal OSC message handler</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">handle_message</span>(self, message):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        self<span style="color:#555">.</span>last_incoming <span style="color:#555">=</span> time<span style="color:#555">.</span>time() <span style="color:#09f;font-style:italic"># Reset the last incoming timer</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        <span style="color:#069;font-weight:bold">if</span> message<span style="color:#555">.</span>address <span style="color:#555">==</span> <span style="color:#c30">&#34;/subscribed/faders&#34;</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            self<span style="color:#555">.</span>handle_message__subscribed_faders(message)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>            <span style="color:#069;font-weight:bold">return</span>  <span style="color:#09f;font-style:italic"># Subscribed aliases are internal message no need to forward it</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        <span style="color:#069;font-weight:bold">if</span> message<span style="color:#555">.</span>address <span style="color:#555">==</span> <span style="color:#c30">&#34;/node&#34;</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            self<span style="color:#555">.</span>handle_message__node(message)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            <span style="color:#069;font-weight:bold">return</span>  <span style="color:#09f;font-style:italic"># Can&#39;t forward node responses</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>        <span style="color:#09f;font-style:italic"># If the address is &#34;/info&#34; retrieve the server version/name and the console model/version and trigger the connection handlers</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        <span style="color:#069;font-weight:bold">if</span> message<span style="color:#555">.</span>address <span style="color:#555">==</span> <span style="color:#c30">&#34;/info&#34;</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>            self<span style="color:#555">.</span>handle_message__info(message)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        <span style="color:#09f;font-style:italic"># The message to every other handler</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        <span style="color:#069;font-weight:bold">for</span> handler <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>handlers:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>            handler(message)
</span></span></code></pre></div><p>The <code>/info</code> handler is basic. It simply extracts the information, stores it and calls every &quot;connection&quot; handler.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">handle_message__info</span>(self, message):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        self<span style="color:#555">.</span>x32_server_version <span style="color:#555">=</span> message<span style="color:#555">.</span>params[<span style="color:#f60">0</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        self<span style="color:#555">.</span>x32_server_name <span style="color:#555">=</span> message<span style="color:#555">.</span>params[<span style="color:#f60">1</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        self<span style="color:#555">.</span>x32_console_model <span style="color:#555">=</span> message<span style="color:#555">.</span>params[<span style="color:#f60">2</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        self<span style="color:#555">.</span>x32_console_version <span style="color:#555">=</span> message<span style="color:#555">.</span>params[<span style="color:#f60">3</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>        <span style="color:#069;font-weight:bold">for</span> handler <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>connection_handlers:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>             handler(self<span style="color:#555">.</span>has_connection)
</span></span></code></pre></div><p>The handler for <code>/node</code> responses is a bit more complex. The node messages are basically an OSC message converted to a string and terminated by a newline. The only (string) argument looks a bit like this <code>/-prefs/iQ/01 none &quot;Linear&quot; 0\n</code>. To parse it, I used the <code>shlex</code> module to do lexical analysis of the received text (because the quoted text might contain space, I can't use a simple split). Using this method, the address is the first element, then I check if every parameter is a float or an int, if not, it defaults to a string. Then, I call the <code>handle_message</code> recursively to redistribute the message.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">handle_message__node</span>(self, message):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        data <span style="color:#555">=</span> shlex<span style="color:#555">.</span>split(message<span style="color:#555">.</span>params[<span style="color:#f60">0</span>][:<span style="color:#555">-</span><span style="color:#f60">1</span>])
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        msg <span style="color:#555">=</span> OscMessageBuilder(data[<span style="color:#f60">0</span>])
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        <span style="color:#069;font-weight:bold">for</span> param <span style="color:#000;font-weight:bold">in</span> data[<span style="color:#f60">1</span>:]:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>            msg<span style="color:#555">.</span>add_arg((<span style="color:#366">int</span>(<span style="color:#366">float</span>(param)) <span style="color:#069;font-weight:bold">if</span> <span style="color:#366">float</span>(param)<span style="color:#555">.</span>is_integer() <span style="color:#069;font-weight:bold">else</span> <span style="color:#366">float</span>(param)) <span style="color:#069;font-weight:bold">if</span> _is_number_tryexcept(param) <span style="color:#069;font-weight:bold">else</span> param)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>        self<span style="color:#555">.</span>handle_message(msg<span style="color:#555">.</span>build())
</span></span></code></pre></div><p>The <code>/formatsubscribe</code> command will send one binary blob message instead of multiple messages for everything subscribed. Since I subscribe to <code>/ch/**/mix/on</code> and <code>/ch/**/mix/fader</code> with a start of <code>1</code> and an end of <code>32</code> the console sends a binary blob consisting of 32 int32 for the mute status and 32 float32 for the fader values. Decoding this is straightforward thanks to the <code>struct</code> module and this format <code>&lt;i32i32f</code>. Then I again call the <code>handle_message</code> recursively with &quot;fake&quot; messages</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>    <span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">handle_message__subscribed_faders</span>(self, message):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>        <span style="color:#069;font-weight:bold">if</span> <span style="color:#366">len</span>(message<span style="color:#555">.</span>params[<span style="color:#f60">0</span>]) <span style="color:#555">!=</span> (<span style="color:#f60">4</span> <span style="color:#555">+</span> <span style="color:#f60">4</span> <span style="color:#555">*</span> <span style="color:#f60">32</span> <span style="color:#555">+</span> <span style="color:#f60">4</span> <span style="color:#555">*</span> <span style="color:#f60">32</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>            self<span style="color:#555">.</span>_log<span style="color:#555">.</span>warning(<span style="color:#c30">&#34;Messaged recevied by the /subscribed/faders handler that doesn&#39;t have the right size ({len(message.params[0])} != 260)&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>            <span style="color:#069;font-weight:bold">return</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        data <span style="color:#555">=</span> struct<span style="color:#555">.</span>unpack(<span style="color:#c30">f</span><span style="color:#c30">&#34;&lt;i32i32f&#34;</span>, message<span style="color:#555">.</span>params[<span style="color:#f60">0</span>])[<span style="color:#f60">1</span>:]  <span style="color:#09f;font-style:italic"># Ignore first byte which is the length</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        <span style="color:#09f;font-style:italic"># Fake messages to maintain compatibility</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">for</span> i, value <span style="color:#000;font-weight:bold">in</span> <span style="color:#366">enumerate</span>(data[:<span style="color:#f60">32</span>]):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            msg <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>i<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/mix/on&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            msg<span style="color:#555">.</span>add_arg(value)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            self<span style="color:#555">.</span>handle_message(msg<span style="color:#555">.</span>build())
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        <span style="color:#069;font-weight:bold">for</span> i, value <span style="color:#000;font-weight:bold">in</span> <span style="color:#366">enumerate</span>(data[<span style="color:#f60">32</span>:]):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            msg <span style="color:#555">=</span> OscMessageBuilder(<span style="color:#c30">f</span><span style="color:#c30">&#34;/ch/</span><span style="color:#a00">{</span>i<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#a00">:</span><span style="color:#c30">02</span><span style="color:#a00">}</span><span style="color:#c30">/mix/fader&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>            msg<span style="color:#555">.</span>add_arg(value)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>            self<span style="color:#555">.</span>handle_message(msg<span style="color:#555">.</span>build())
</span></span></code></pre></div><h4 id="main-loop">
    Main loop 
    
    <a class="header-link" href="#main-loop">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>This loop is responsible for:</p>
<ul>
<li>Calling the <code>_re_sync</code> function every 60sec</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>             <span style="color:#069;font-weight:bold">if</span> self<span style="color:#555">.</span>last_resync <span style="color:#555">+</span> <span style="color:#f60">60</span> <span style="color:#555">&lt;</span> time<span style="color:#555">.</span>time():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>                self<span style="color:#555">.</span>_re_sync()
</span></span></code></pre></div><ul>
<li>Calling the <code>_re_subscribe</code> function every 5sec</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>             <span style="color:#069;font-weight:bold">if</span> self<span style="color:#555">.</span>last_resubscribe <span style="color:#555">+</span> <span style="color:#f60">5</span> <span style="color:#555">&lt;</span> time<span style="color:#555">.</span>time():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>                self<span style="color:#555">.</span>_re_subscribe()
</span></span></code></pre></div><ul>
<li>Checking that it's still connected to the X32</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>            <span style="color:#09f;font-style:italic"># Connection status check</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>            <span style="color:#069;font-weight:bold">if</span> last_connection_status <span style="color:#555">!=</span> self<span style="color:#555">.</span>has_connection:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>                last_connection_status <span style="color:#555">=</span> self<span style="color:#555">.</span>has_connection
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>                <span style="color:#09f;font-style:italic"># Notify handlers</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>                <span style="color:#069;font-weight:bold">for</span> handler <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>connection_handlers:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>                    handler(self<span style="color:#555">.</span>has_connection)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>                <span style="color:#09f;font-style:italic"># Force a resync if we just connected</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span>                <span style="color:#069;font-weight:bold">if</span> self<span style="color:#555">.</span>has_connection:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span>                    self<span style="color:#555">.</span>_re_sync()
</span></span></code></pre></div><ul>
<li>Receiving data from the socket</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>            <span style="color:#069;font-weight:bold">try</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>                <span style="color:#09f;font-style:italic"># A while loop here will ensure that if there is still data incoming it will be read before processing re-syncs</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>                <span style="color:#09f;font-style:italic"># recvfrom will throw an error if there isn&#39;t anything thus exiting the loop</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>                <span style="color:#069;font-weight:bold">while</span> <span style="color:#069;font-weight:bold">True</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>                    <span style="color:#09f;font-style:italic"># Receive data from the socket</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>                    data, peer <span style="color:#555">=</span> self<span style="color:#555">.</span>_socket<span style="color:#555">.</span>recvfrom(<span style="color:#f60">1024</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>                    <span style="color:#09f;font-style:italic"># If data is present, and it&#39;s an OSC message, send it to the internal handler</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>                    <span style="color:#069;font-weight:bold">if</span> data <span style="color:#000;font-weight:bold">and</span> OscMessage<span style="color:#555">.</span>dgram_is_message(data):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                        self<span style="color:#555">.</span>handle_message(OscMessage(data))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>                    <span style="color:#09f;font-style:italic"># The message might also be a &#34;/node&#34; message from the console. These message don&#39;t start with / and thus don&#39;t comply with the OCS standard.</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                    <span style="color:#09f;font-style:italic"># Manually correct the address and send it to the internal handler</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>                    <span style="color:#069;font-weight:bold">elif</span> data<span style="color:#555">.</span>startswith(<span style="color:#c30">b</span><span style="color:#c30">&#34;node&#34;</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                        data <span style="color:#555">=</span> data<span style="color:#555">.</span>replace(<span style="color:#c30">b</span><span style="color:#c30">&#34;node</span><span style="color:#c30;font-weight:bold">\x00\x00\x00\x00</span><span style="color:#c30">&#34;</span>, <span style="color:#c30">b</span><span style="color:#c30">&#34;/node</span><span style="color:#c30;font-weight:bold">\x00\x00\x00</span><span style="color:#c30">&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                        self<span style="color:#555">.</span>handle_message(OscMessage(data))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>                    <span style="color:#09f;font-style:italic"># If data is present, and it&#39;s an OSC bundle, unpack it and send all message to the internal handler</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>                    <span style="color:#069;font-weight:bold">elif</span> data <span style="color:#000;font-weight:bold">and</span> OscBundle<span style="color:#555">.</span>dgram_is_bundle(data):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>                        <span style="color:#069;font-weight:bold">for</span> message <span style="color:#000;font-weight:bold">in</span> OscBundle(data):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>                            self<span style="color:#555">.</span>handle_message(message)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>                    <span style="color:#069;font-weight:bold">else</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>                        self<span style="color:#555">.</span>_log<span style="color:#555">.</span>error(<span style="color:#c30">f</span><span style="color:#c30">&#34;Received invalid data: </span><span style="color:#a00">{</span>data<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>            <span style="color:#069;font-weight:bold">except</span> <span style="color:#c00;font-weight:bold">BlockingIOError</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>                <span style="color:#09f;font-style:italic"># Ignore BlockingIO errors</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>                <span style="color:#069;font-weight:bold">pass</span>
</span></span></code></pre></div><p>Quick note on the <code>/node</code> message: Unfortunately, Midas/Behringer didn't fully follow the OSC spec and the <code>node</code> messages don't start with the <code>/</code> 😕, they are padded properly though, so all it takes is a simple <code>.replace</code> before handling the message 🎉.</p>
<p>In the <code>__main__</code> of the module, I simply start an MQTT client and the M32 client and forward every incoming message OSC to MQTT with the topic prefixed by <code>modules/osc</code>:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">forward_to_mqtt</span>(message: OscMessage):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    client<span style="color:#555">.</span>publish(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        topic<span style="color:#555">=</span><span style="color:#c30">f</span><span style="color:#c30">&#34;modules/osc</span><span style="color:#a00">{</span>message<span style="color:#555">.</span>address<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        payload<span style="color:#555">=</span>json<span style="color:#555">.</span>dumps(message<span style="color:#555">.</span>params),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        retain<span style="color:#555">=</span><span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>    )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#09f;font-style:italic"># Define the function that sends out the status of the module</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">publish_connection_status</span>(is_connected):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>    client<span style="color:#555">.</span>publish(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>        topic<span style="color:#555">=</span><span style="color:#c30">f</span><span style="color:#c30">&#34;modules/osc/status&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>        payload<span style="color:#555">=</span>json<span style="color:#555">.</span>dumps({
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>            <span style="color:#c30">&#34;connected&#34;</span>: is_connected,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>            <span style="color:#c30">&#34;x32_server_version&#34;</span>: x32<span style="color:#555">.</span>x32_server_version,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>            <span style="color:#c30">&#34;x32_server_name&#34;</span>: x32<span style="color:#555">.</span>x32_server_name,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>            <span style="color:#c30">&#34;x32_console_model&#34;</span>: x32<span style="color:#555">.</span>x32_console_model,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>            <span style="color:#c30">&#34;x32_console_version&#34;</span>: x32<span style="color:#555">.</span>x32_console_version,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>        }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>        retain<span style="color:#555">=</span><span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>    )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>x32 <span style="color:#555">=</span> X32(x32_address)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>x32<span style="color:#555">.</span>handlers<span style="color:#555">.</span>append(forward_to_mqtt)  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>x32<span style="color:#555">.</span>connection_handlers<span style="color:#555">.</span>append(publish_connection_status)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>x32<span style="color:#555">.</span>start()
</span></span></code></pre></div><p>Note that the message is published with the <code>retain=True</code> this means that the server will keep a copy of the last message and will send the last value every time a client subscribes to the topic</p>
<h3 id="2nd-module-inputs">
    2nd module: Inputs 
    
    <a class="header-link" href="#2nd-module-inputs">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>That module is dead simple, t's only job is simple to send out any updates to the MQTT server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">while</span> <span style="color:#069;font-weight:bold">True</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">for</span> ch, input_channel <span style="color:#000;font-weight:bold">in</span> config<span style="color:#555">.</span>input_channels<span style="color:#555">.</span>items():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#09f;font-style:italic"># Get (if possible) the value of the buttons, Defaults to None if the channel does not have a button</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        channel_value <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">None</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        <span style="color:#069;font-weight:bold">if</span> <span style="color:#c30">&#34;on_stand_button&#34;</span> <span style="color:#000;font-weight:bold">in</span> input_channel:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            channel_value <span style="color:#555">=</span> inputs<span style="color:#555">.</span>get(input_channel[<span style="color:#c30">&#34;on_stand_button&#34;</span>])
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        <span style="color:#09f;font-style:italic"># If the status has changed, publish the values over MQTT</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        <span style="color:#069;font-weight:bold">if</span> ch <span style="color:#000;font-weight:bold">not</span> <span style="color:#000;font-weight:bold">in</span> last_channel_values <span style="color:#000;font-weight:bold">or</span> last_channel_values[ch] <span style="color:#555">!=</span> channel_value:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            last_channel_values[ch] <span style="color:#555">=</span> channel_value
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>            client<span style="color:#555">.</span>publish(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                topic<span style="color:#555">=</span><span style="color:#c30">f</span><span style="color:#c30">&#34;modules/stand_buttons/</span><span style="color:#a00">{</span>ch<span style="color:#a00">:</span><span style="color:#c30">02d</span><span style="color:#a00">}</span><span style="color:#c30">/status&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>                payload<span style="color:#555">=</span>json<span style="color:#555">.</span>dumps({
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                    <span style="color:#c30">&#34;enabled&#34;</span>: input_channel[<span style="color:#c30">&#34;enabled&#34;</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                    <span style="color:#c30">&#34;has_button&#34;</span>: <span style="color:#c30">&#34;on_stand_button&#34;</span> <span style="color:#000;font-weight:bold">in</span> input_channel,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>                    <span style="color:#c30">&#34;value&#34;</span>: channel_value,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>                    <span style="color:#c30">&#34;last_update&#34;</span>: time<span style="color:#555">.</span>time()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>                }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>                retain<span style="color:#555">=</span><span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>            )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>            client<span style="color:#555">.</span>publish(<span style="color:#c30">f</span><span style="color:#c30">&#34;ONSTAND_DETECTION/ch/</span><span style="color:#a00">{</span>ch<span style="color:#a00">:</span><span style="color:#c30">02d</span><span style="color:#a00">}</span><span style="color:#c30">/is_on_stand&#34;</span>, last_status[ch], retain<span style="color:#555">=</span><span style="color:#069;font-weight:bold">True</span>)
</span></span></code></pre></div><p>It loops through all channels that have input configured and check if the microphone is in the holder. Only if the current value is different from the old value, it sends an update to the <code>modules/stand_buttons/XX/status</code> with a JSON dict containing:</p>
<ul>
<li>If the channel is enabled,</li>
<li>If it has a button,</li>
<li>The value of said button,</li>
<li>The last time it was updated.</li>
</ul>
<p>Same as before, the message is published with the <code>retain=True</code> which means that the server will keep a copy of the last message.</p>
<h3 id="3rd-module-leds">
    3rd module: LEDs 
    
    <a class="header-link" href="#3rd-module-leds">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>This module is basic but a bit more involved as it needs to keep a history of the messages received.
In simple terms, every time a message is received, it's put in a dict with the key being the topic.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>message_history <span style="color:#555">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">on_message</span>(client, userdata, msg):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>	<span style="color:#069;font-weight:bold">global</span> message_history
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>	<span style="color:#069;font-weight:bold">try</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>		message_history[msg<span style="color:#555">.</span>topic] <span style="color:#555">=</span> json<span style="color:#555">.</span>loads(msg<span style="color:#555">.</span>payload)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>	<span style="color:#069;font-weight:bold">except</span> json<span style="color:#555">.</span>decoder<span style="color:#555">.</span>JSONDecodeError <span style="color:#069;font-weight:bold">as</span> e:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>		<span style="color:#069;font-weight:bold">pass</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>		
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>client<span style="color:#555">.</span>on_message  <span style="color:#555">=</span>  on_message
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">try_get</span>(obj, topic):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>	<span style="color:#069;font-weight:bold">if</span> topic <span style="color:#000;font-weight:bold">in</span> obj:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>		<span style="color:#069;font-weight:bold">return</span> obj[topic]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>	<span style="color:#069;font-weight:bold">return</span> <span style="color:#069;font-weight:bold">None</span>
</span></span></code></pre></div><p>To avoid iterating over a channel that doesn't have LEDs, I added a filter that creates a dict with only channels that have LEDs:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>input_channels_with_leds <span style="color:#555">=</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>	ch: input_channel
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>	<span style="color:#069;font-weight:bold">for</span> ch, input_channel <span style="color:#000;font-weight:bold">in</span> config<span style="color:#555">.</span>input_channels<span style="color:#555">.</span>items()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>	<span style="color:#069;font-weight:bold">if</span> <span style="color:#c30">&#34;tally_leds&#34;</span> <span style="color:#000;font-weight:bold">in</span> input_channel
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>}
</span></span></code></pre></div><p>Then there are two functions.
One will do an animation with the LEDs and is used when the OSC module reports that it cannot connect to the M32. This animation is a simple ping-pong style animation with LEDs.</p>
<p>The more interesting function is the <code>do_tally_lights</code> function. This one is responsible for looping over every channel with an LED:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">do_tally_lights</span>():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>    <span style="color:#09f;font-style:italic"># Loop over every channel that have LEDs</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>    <span style="color:#069;font-weight:bold">for</span> ch, input_channel <span style="color:#000;font-weight:bold">in</span> input_channels_with_leds<span style="color:#555">.</span>items():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        <span style="color:#09f;font-style:italic"># Set the default color to black</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        color <span style="color:#555">=</span> [<span style="color:#f60">0</span>, <span style="color:#f60">0</span>, <span style="color:#f60">0</span>]
</span></span></code></pre></div><p>Getting the vales from the history</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>        <span style="color:#09f;font-style:italic"># Get the mute, fader and is_on_stand values from the history</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        x32_on <span style="color:#555">=</span> try_get(message_history, <span style="color:#c30">f</span><span style="color:#c30">&#34;modules/osc/ch/</span><span style="color:#a00">{</span>ch<span style="color:#a00">:</span><span style="color:#c30">02d</span><span style="color:#a00">}</span><span style="color:#c30">/mix/on&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        x32_fader <span style="color:#555">=</span> try_get(message_history, <span style="color:#c30">f</span><span style="color:#c30">&#34;modules/osc/ch/</span><span style="color:#a00">{</span>ch<span style="color:#a00">:</span><span style="color:#c30">02d</span><span style="color:#a00">}</span><span style="color:#c30">/mix/fader&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        module_is_on_stand <span style="color:#555">=</span> try_get(message_history, <span style="color:#c30">f</span><span style="color:#c30">&#34;modules/stand_buttons/</span><span style="color:#a00">{</span>ch<span style="color:#a00">:</span><span style="color:#c30">02d</span><span style="color:#a00">}</span><span style="color:#c30">/status&#34;</span>)
</span></span></code></pre></div><p>Checking that the channel is actually enabled</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>        <span style="color:#09f;font-style:italic"># If the channel is enabled</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        <span style="color:#069;font-weight:bold">if</span> input_channel[<span style="color:#c30">&#34;enabled&#34;</span>]:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>            <span style="color:#09f;font-style:italic"># If the value stored in the history for the mute and fader values are not None</span>
</span></span></code></pre></div><p>Checking that there is, a history for either the <code>fader</code> or the <code>mute</code> status, If there isn't that either means that the channel doesn't exist or wasn't updated yet. I then set the LEDs to black in this case.</p>
<p>On the other hand, if I have a value I then determine if the channel is active, meaning unmuted and a fader being at more than 8%. If it's active, I turn the LEDs green and set the LEDs to red if inactive.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>            <span style="color:#069;font-weight:bold">if</span> x32_on <span style="color:#000;font-weight:bold">is</span> <span style="color:#000;font-weight:bold">not</span> <span style="color:#069;font-weight:bold">None</span> <span style="color:#000;font-weight:bold">and</span> x32_fader <span style="color:#000;font-weight:bold">is</span> <span style="color:#000;font-weight:bold">not</span> <span style="color:#069;font-weight:bold">None</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>                <span style="color:#09f;font-style:italic"># Calculate if the channel is active</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>                is_active <span style="color:#555">=</span> x32_on[<span style="color:#f60">0</span>] <span style="color:#000;font-weight:bold">and</span> x32_fader[<span style="color:#f60">0</span>] <span style="color:#555">&gt;</span> <span style="color:#f60">0.08</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>                <span style="color:#09f;font-style:italic"># Set the channel color</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>                color <span style="color:#555">=</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;active&#34;</span>] <span style="color:#069;font-weight:bold">if</span> is_active <span style="color:#069;font-weight:bold">else</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;muted&#34;</span>]
</span></span></code></pre></div><p>Then, it checks if there is a history for the &quot;is on stand&quot; value. If the microphone is <strong>on the holder</strong> but <strong>is active</strong> or if the microphone is <strong>not</strong> on the holder and <strong>inactive</strong>, I blink the LEDs brightness to signal an issue.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>                <span style="color:#09f;font-style:italic"># if the value stored in the history for the is_on_stand is not None</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>                <span style="color:#069;font-weight:bold">if</span> module_is_on_stand <span style="color:#000;font-weight:bold">is</span> <span style="color:#000;font-weight:bold">not</span> <span style="color:#069;font-weight:bold">None</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>                    <span style="color:#09f;font-style:italic"># If the channel is active and in the stand or not active and not in the stand</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>                    <span style="color:#069;font-weight:bold">if</span> module_is_on_stand[<span style="color:#c30">&#34;value&#34;</span>] <span style="color:#555">==</span> is_active:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>                        <span style="color:#09f;font-style:italic"># Math tricks to make it blink slower</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>                        <span style="color:#069;font-weight:bold">if</span> <span style="color:#366">int</span>(time<span style="color:#555">.</span>time() <span style="color:#555">*</span> <span style="color:#f60">5</span>) <span style="color:#555">%</span> <span style="color:#f60">2</span> <span style="color:#555">==</span> <span style="color:#f60">0</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>                            <span style="color:#09f;font-style:italic"># Set the bright color to the channel</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>                            color <span style="color:#555">=</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;active_in_stand_on&#34;</span>] <span style="color:#069;font-weight:bold">if</span> is_active <span style="color:#069;font-weight:bold">else</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;muted_not_in_stand_on&#34;</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>                        <span style="color:#069;font-weight:bold">else</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                            color <span style="color:#555">=</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;active_in_stand_off&#34;</span>] <span style="color:#069;font-weight:bold">if</span> is_active <span style="color:#069;font-weight:bold">else</span> config<span style="color:#555">.</span>tally_colors[<span style="color:#c30">&#34;muted_not_in_stand_off&#34;</span>]
</span></span></code></pre></div><p>Then to finish, I set the color and set the LEDs:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>    leds<span style="color:#555">.</span>set(leds<span style="color:#555">=</span>input_channel[<span style="color:#c30">&#34;tally_leds&#34;</span>], r<span style="color:#555">=</span>color[<span style="color:#f60">0</span>], g<span style="color:#555">=</span>color[<span style="color:#f60">1</span>], b<span style="color:#555">=</span>color[<span style="color:#f60">2</span>])
</span></span></code></pre></div><p>Then in the main loop, I check the status of the OSC module, choose the correct animation and update the LEDs</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">while</span> <span style="color:#069;font-weight:bold">True</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#09f;font-style:italic"># Get the OSC module status</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    osc_status <span style="color:#555">=</span> try_get(message_history, <span style="color:#c30">f</span><span style="color:#c30">&#34;modules/osc/status&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>    <span style="color:#09f;font-style:italic"># Test if the OSC module is connected and execute the proper function</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>    <span style="color:#069;font-weight:bold">if</span> osc_status <span style="color:#000;font-weight:bold">is</span> <span style="color:#069;font-weight:bold">None</span> <span style="color:#000;font-weight:bold">or</span> <span style="color:#000;font-weight:bold">not</span> osc_status[<span style="color:#c30">&#34;connected&#34;</span>]:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        do_disconnected_animation()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>    <span style="color:#069;font-weight:bold">else</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        do_tally_lights()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>    <span style="color:#09f;font-style:italic"># Update the leds</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>    leds<span style="color:#555">.</span>update()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>    time<span style="color:#555">.</span>sleep(<span style="color:#f60">0.05</span>)
</span></span></code></pre></div><p>I might do a third color if the channel is unmuted, but the fader is down. This would signal that the sound engineer is ready for the next part, and is about to enable them.</p>
<h3 id="4th-module-web-interface">
    4th &quot;module&quot;: Web interface 
    
    <a class="header-link" href="#4th-module-web-interface">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>One thing that would be very useful is the ability to see the status of the microphones in the mixing booth. To accomplish that, I wrote a basic app based on Vue.js that uses MQTT thanks to Websockets and the <code>mqttjs</code> library.</p>
<p>The web app subscribes to the data of all 32 channels (meaning name, icon, mute status and fader value) from MQTT and displays it nicely. The On stand detection is also synced from MQTT if available and will stay gray otherwise.</p>
<p>As a sidenote, getting these stupid icons was not fun, while they are in the spec sheet of the unofficial M32 OSC doc, it's one big image not 70+ individual images. I had to dig out some old OpenCV code to detect the 64×64 black squares and extract them manually. Then, thanks to ImageMagick, I applied a black to alpha filter to get rid of the background. <em>The individual images can be found in the GitHub repo.</em></p>
<h4 id="screenshot">
    Screenshot 
    
    <a class="header-link" href="#screenshot">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Thanks to Vuetify, I didn't need to do a lot of CSS, just for the strips themselves. Once this UI placement was finished, I had a very nice read-only UI that even works on phone (kind of, you need to scroll a lot since I chose to force the cue list open on every device):</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_pn_2023_03_06_09-27-32_p4w6vFiaUD.png" >
            <img alt="" src="/microphone-tally-lights/images/dl_pn_2023_03_06_09-27-32_p4w6vFiaUD.png" />
        </a>
        
    </figure>

</p>
<p>A strip surrounded in blue means that someone has the microphone in his/her hand, but it's still muted. Meanwhile, a yellow border means that it's unmuted on the stand. If a strip doesn't match any of these conditions, the strip reflects the color stored in the X32.</p>
<h4 id="web-server">
    Web server 
    
    <a class="header-link" href="#web-server">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>I then built the project to get only static files and started the configuration of the web server.
I went with Caddy because it works, it's simple, and the docs are pleasant, and I'm familiar with it.</p>
<p>After following the Debian install instruction from <a target="_blank" href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">https://caddyserver.com/docs/install#debian-ubuntu-raspbian</a>, I edited the config file as <code>/etc/caddy/Caddyfile</code> it now looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#030;font-weight:bold">:80</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>	<span style="color:#069;font-weight:bold">root</span> <span style="color:#99f">*</span> <span style="color:#c30">/opt/x32_tally/x32_tally/spa_webui/dist/</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>	<span style="color:#069;font-weight:bold">reverse_proxy</span> <span style="color:#99f">/mqtt</span> 127.0.0.1:<span style="color:#f60">8080</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>}
</span></span></code></pre></div><p>By default, it uses the files located in <code>/opt/x32_tally/x32_tally/spa_webui/dist/</code> (where the built files are) and also proxies <code>/mqtt</code> to the mosquito Websocket server</p>
<h3 id="running-as-services">
    Running as services 
    
    <a class="header-link" href="#running-as-services">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>All services are pretty similar, for example, the OSC module looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">[Unit]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#309">Description</span><span style="color:#555">=</span><span style="color:#c30">X32Tally OSC module</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#309">After</span><span style="color:#555">=</span><span style="color:#c30">syslog.target network.target mosquitto.service</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#069;font-weight:bold">[Service]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#309">Type</span><span style="color:#555">=</span><span style="color:#c30">simple</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#309">User</span><span style="color:#555">=</span><span style="color:#c30">root</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#309">WorkingDirectory</span><span style="color:#555">=</span><span style="color:#c30">/opt/x32_tally</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#309">ExecStart</span><span style="color:#555">=</span><span style="color:#c30">python -m x32_tally.osc</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#309">Restart</span><span style="color:#555">=</span><span style="color:#c30">on-failure</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#069;font-weight:bold">[Install]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#309">WantedBy</span><span style="color:#555">=</span><span style="color:#c30">default.target</span>
</span></span></code></pre></div><p>I'm aware that running scripts as root is not the best idea ever. However, I wanted to avoid fiddling with making GPIOs accessible to non-root users, it was just easier this way.</p>
<p>It's something that I would definitively improve have I had more time.</p>
<h3 id="read-only-file-system">
    Read-only file system 
    
    <a class="header-link" href="#read-only-file-system">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As I mentioned before, the system needs to withstand power cuts. The poor SD card isn't the best thing for the job in the first place, so I choose to make the system read-only. I followed this nicely written guide at <a target="_blank" href="https://medium.com/@andreas.schallwig/how-to-make-your-raspberry-pi-file-system-read-only-raspbian-stretch-80c0f7be7353">https://medium.com/@andreas.schallwig/how-to-make-your-raspberry-pi-file-system-read-only-raspbian-stretch-80c0f7be7353</a></p>
<h2 id="functional-diagram">
    Functional diagram 
    
    <a class="header-link" href="#functional-diagram">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that I had finished, I spent some time doing a diagram that shows how everything interconnects
.

    <figure>
        <a target="_blank" href="images/dl_diagram.drawio.png" >
            <img alt="Functional diagram" src="/microphone-tally-lights/images/dl_diagram.drawio.png" />
        </a>
        
    </figure>

</p>
<h2 id="and-in-use">
    And in use? 
    
    <a class="header-link" href="#and-in-use">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Last week was the first time this system was actually used in production:


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_signal-2023-03-01-172743_003.jpeg">
                            <img src="/microphone-tally-lights/images/dl_signal-2023-03-01-172743_003_hucc51c7e94d6e739e83afb96faa4f3faf_230384_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_DSC00762_crop.jpg">
                            <img src="/microphone-tally-lights/images/dl_DSC00762_crop_hu86eaa8b7e56f5c6be603095749f73d00_2642041_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div></p>
<p>And it worked perfectly.</p>
<p>Or that's what I would have said were it not for that stupid network card I used 🤬. That thing worked for 1h and decided to completely stop working right when everyone started to come in.</p>
<p>I don't know the issue for sure, but someone mentioned that it might be due to a potential difference between the power I used, and the one used for everything else. It shouldn't really happen because Ethernet is supposed to be isolated, I guess it wasn't 🤔.</p>
<p>I had to carve out a hole in the controller box to be able to plug a micro USB OTG adapter and plugging a better network card in. I'll re-print everything with a different slot for another network card. Hopefully, that won't happen again.</p>
<p><strong>Apart from that, everything was perfect. Some responses I got range from good to super awesome. 🥳😃</strong></p>
<p>It allowed everyone in the mixing booth to see if we kept a mic open or if forgot to open someone. Backstage, it allowed techs to more easily visualize which microphone was taken at a glance. It also alerted them immediately if there were discrepancies from the mic-sheet.</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This system is wonderful, it facilitates the job of the backstage techs and gives more info in the mixing booth. I bet there are quite a few places where this could be very useful.</p>
<p>I think it would even be possible to implement some kind of auto-mixer where it would unmute automatically channels when the microphone is in someone's hand.</p>
<p>I do think that the biggest improvement I could make is using a custom PCB for the holders and printing it in a different material.</p>
<p>Overall, very pleased with this project 😄.</p>

 ]]></content:encoded></item><item><title>Sending notifications on user login</title><description> &lt;p>Writing a small bash script to send a notification in my telegram monitoring channel each time a user logs in to one of my server&lt;/p></description><link>https://blog.thestaticturtle.fr/sending-notifications-on-user-login/</link><guid>https://blog.thestaticturtle.fr/sending-notifications-on-user-login/</guid><category> Homelab</category><category> Security</category><dc:creator> Samuel</dc:creator><pubDate>Wed, 01 Mar 2023 11:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/sending-notifications-on-user-login/images/cover_hu0676cfd4282bfa0e66c698442e0a6bc8_865479_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/sending-notifications-on-user-login/images/cover_hu0676cfd4282bfa0e66c698442e0a6bc8_865479_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Sending notifications on user login</h1>
<span class="subtitle"><p>Writing a small bash script to send a notification in my telegram monitoring channel each time a user logs in to one of my server</p></span>
<br>

    <img class="" src='/sending-notifications-on-user-login/images/cover_hu0676cfd4282bfa0e66c698442e0a6bc8_865479_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Sending notifications on user login"/>

<hr>

<p>I thought it was a nice little idea to set up a script that notifies my when a user logins into one of my most important VMs/CTs/Hosts. Of course there are probably ways around it, but it was somewhat fun to write. So let's dive in!</p>
<h2 id="notifications">
    Notifications 
    
    <a class="header-link" href="#notifications">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>For the notifications, I choose Telegram, mostly because I already have most of my notifications in there already and because the API is just so simple 😲</p>
<h3 id="creating-the-bot">
    Creating the bot 
    
    <a class="header-link" href="#creating-the-bot">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Telegram makes it ridiculously easy to create a new bot, so I just dm-ed @BotFather and got a new token.

    <figure>
        <a target="_blank" href="images/dl_Telegram_2022-12-06_18-39-45_5d37e168-01ae-4654-bd1e-66c75967aa00.png" >
            <img alt="enter image description here" src="/sending-notifications-on-user-login/images/dl_Telegram_2022-12-06_18-39-45_5d37e168-01ae-4654-bd1e-66c75967aa00.png" />
        </a>
        
    </figure>

</p>
<p>Subsequently, all, I had to do is add the bot to the group I use for monitoring. Then I could use the API myself to determine the chat ID of my group:</p>
<p>I simply went to <code>https://api.telegram.org/botTELEGRAMBOTTOKEN/getUpdates</code> in my browser (with <code>TELEGRAMBOTTOKEN</code> being the real token) and got the chat id:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2022-12-06_18-45-13_cc900e57-a688-4c85-9c3d-2ccf118b31c2.png" >
            <img alt="enter image description here" src="/sending-notifications-on-user-login/images/dl_chrome_2022-12-06_18-45-13_cc900e57-a688-4c85-9c3d-2ccf118b31c2.png" />
        </a>
        
    </figure>

</p>
<p>On Telegram, chat ID are negative, and user IDs are positive, so it was pretty easy to find.</p>
<p>I then ran:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>curl -X POST <span style="color:#c30;font-weight:bold">\
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#c30;font-weight:bold"></span>     -H <span style="color:#c30">&#39;Content-Type: application/json&#39;</span> <span style="color:#c30;font-weight:bold">\
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#c30;font-weight:bold"></span>     -d <span style="color:#c30">&#39;{&#34;chat_id&#34;: &#34;&#39;</span><span style="color:#c30">&#34;</span><span style="color:#033">$TELEGRAM_CHAT_ID</span><span style="color:#c30">&#34;</span><span style="color:#c30">&#39;&#34;, &#34;text&#34;: &#34;This is a test from curl&#34;, &#34;disable_notification&#34;: true}&#39;</span> <span style="color:#c30;font-weight:bold">\
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#c30;font-weight:bold"></span>     https://api.telegram.org/bot<span style="color:#033">$TELEGRAM_BOT_TOKEN</span>/sendMessage
</span></span></code></pre></div><p>With the correct variables, and it worked, nice 😀

    <figure>
        <a target="_blank" href="images/dl_Telegram_2022-12-06_19-26-28_b006873d-1034-4d1e-9e70-71c7cadfecfa.png" >
            <img alt="enter image description here" src="/sending-notifications-on-user-login/images/dl_Telegram_2022-12-06_19-26-28_b006873d-1034-4d1e-9e70-71c7cadfecfa.png" />
        </a>
        
    </figure>

</p>
<h2 id="writing-the-script">
    Writing the script 
    
    <a class="header-link" href="#writing-the-script">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After having a way to send messages, I need to generate what I need to send.</p>
<ul>
<li>Getting the hostname is easy: <code>hostname -f</code></li>
<li>Getting the current user is also pretty easy: <code>$USER</code></li>
<li>Getting the current session took a bit of digging, but it's also pretty easy: <code>who -m</code></li>
</ul>
<p>So, I wrote a little script to combine everything and send the message:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#033">TELEGRAM_CHAT_ID</span><span style="color:#555">=</span><span style="color:#c30">&#34;-----censored-----&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#033">TELEGRAM_BOT_TOKEN</span><span style="color:#555">=</span><span style="color:#c30">&#34;-----censored-----&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#033">HOSTNAME</span><span style="color:#555">=</span><span style="color:#069;font-weight:bold">$(</span>hostname -f<span style="color:#069;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#033">WHO_OUT</span><span style="color:#555">=</span><span style="color:#069;font-weight:bold">$(</span>who -m<span style="color:#069;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#033">MESSAGE</span><span style="color:#555">=</span><span style="color:#c30">&#34;User &#34;</span><span style="color:#033">$USER</span><span style="color:#c30">&#34;@&#34;</span><span style="color:#033">$HOSTNAME</span><span style="color:#c30">&#34; logged in from: \n&#34;</span><span style="color:#033">$WHO_OUT</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#033">DATA</span><span style="color:#555">=</span><span style="color:#c30">&#39;{&#34;chat_id&#34;: &#34;&#39;</span><span style="color:#c30">&#34;</span><span style="color:#033">$TELEGRAM_CHAT_ID</span><span style="color:#c30">&#34;</span><span style="color:#c30">&#39;&#34;, &#34;text&#34;: &#34;&#39;</span><span style="color:#c30">&#34;</span><span style="color:#033">$MESSAGE</span><span style="color:#c30">&#34;</span><span style="color:#c30">&#39;&#34;, &#34;disable_notification&#34;: true}&#39;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>curl -X POST -H <span style="color:#c30">&#39;Content-Type: application/json&#39;</span> -d <span style="color:#c30">&#34;</span><span style="color:#033">$DATA</span><span style="color:#c30">&#34;</span> <span style="color:#c30">&#34;https://api.telegram.org/bot</span><span style="color:#033">$TELEGRAM_BOT_TOKEN</span><span style="color:#c30">/sendMessage&#34;</span> 2&gt;/dev/null 1&gt;/dev/null
</span></span></code></pre></div><p>Did a quick test, it worked, nice</p>
<h2 id="deploying-the-script">
    Deploying the script 
    
    <a class="header-link" href="#deploying-the-script">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I'm starting to use Ansible to deploy configs to my servers, so I just created a new role with this task:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>Copy profile script<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">when</span>:<span style="color:#bbb"> </span>ansible_os_family == &#39;Debian&#39;<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">copy</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">src</span>:<span style="color:#bbb"> </span>files/Z99-notify-login.sh<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">dest</span>:<span style="color:#bbb"> </span>/etc/profile.d/Z99-notify-login.sh<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">owner</span>:<span style="color:#bbb"> </span>root<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">group</span>:<span style="color:#bbb"> </span>root<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">mode</span>:<span style="color:#bbb"> </span><span style="color:#f60">0755</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">register</span>:<span style="color:#bbb"> </span>copy_login_notify_script<span style="color:#bbb">
</span></span></span></code></pre></div><p>And added said role the playbook.yaml (parts are redacted, hence why it's in different blocks)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>- <span style="color:#309;font-weight:bold">hosts</span>:<span style="color:#bbb"> </span>core<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">roles</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span>- security_login_notify<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb"></span>- <span style="color:#309;font-weight:bold">hosts</span>:<span style="color:#bbb"> </span>proxmox_pve<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">roles</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">    </span>- security_login_notify<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb"></span>- <span style="color:#309;font-weight:bold">hosts</span>:<span style="color:#bbb"> </span>proxmox_pbs<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">roles</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">    </span>- security_login_notify<span style="color:#bbb">
</span></span></span></code></pre></div><h2 id="demo">
    Demo 
    
    <a class="header-link" href="#demo">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p><a target="_blank" href="https://youtu.be/Lo27E83Gc0I">https://youtu.be/Lo27E83Gc0I</a></p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This is a very little script that, honestly, is just a gimmick, there definitively are ways to bypass it (for example Ansible doesn't trigger it because it doesn't load the profile).</p>
<p>I wrote it mainly to track logins of one machine which is exposed to the outside for me to use as a bastion.</p>

 ]]></content:encoded></item><item><title>Homelab Single-Sign On?</title><description> &lt;p>Installing an SSO solution in my homelab with Authelia to facilitate the user management and reduce the number of logins required.&lt;/p></description><link>https://blog.thestaticturtle.fr/homelab-single-sign-on/</link><guid>https://blog.thestaticturtle.fr/homelab-single-sign-on/</guid><category> Homelab</category><category> Docker</category><category> Security</category><category> Tutorials</category><dc:creator> Samuel</dc:creator><pubDate>Wed, 01 Feb 2023 11:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/homelab-single-sign-on/images/cover_hua416b33e29dbbad0535306a3fc3e49aa_80834_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/homelab-single-sign-on/images/cover_hua416b33e29dbbad0535306a3fc3e49aa_80834_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Homelab Single-Sign On?</h1>
<span class="subtitle"><p>Installing an SSO solution in my homelab with Authelia to facilitate the user management and reduce the number of logins required.</p></span>
<br>

    <img class="" src='/homelab-single-sign-on/images/cover_hua416b33e29dbbad0535306a3fc3e49aa_80834_1350x900_fit_q80_box.jpg' alt="Homelab Single-Sign On?"/>

<hr>

<p>First, what is single-sign-on:</p>
<blockquote>
<p><strong>Single sign-on</strong> (<strong>SSO</strong>) is an authentication scheme that allows a user to login with a single ID to any of several related, yet independent, software systems.</p>
<p>True single sign-on allows the user to log in once and access services without re-entering authentication factors. It should not be confused with same-sign on (Directory Server Authentication), it refers to systems requiring authentication for each application but using the same credentials from a directory server</p>
</blockquote>
<p>In my homelab, I don't really care if I need to reenter my login/password to sign in to an app. So, what I'm actually using is Same-Sign On, however, in this article I'll only show examples of Single-Sign On in this post.</p>
<h2 id="which-one">
    Which one? 
    
    <a class="header-link" href="#which-one">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>There are multiple options, which all have advantages/disadvantages. Here is a list of the one I investigated and my up/downs of each:</p>
<ul>
<li>CAS (Central Authentication Service)
<ul>
<li>Overall really amazing 🔥, it's used by numerous universities in France mainly because the CAS protocol is available on many educational tools like Moodle. However, it's Java-based which already tells a lot, it's extremely resource intensive, especially on the ram. It's also a bit annoying to configure since you need to recompile the thing each time. It supports just about every authentication source and authentication protocol possible</li>
<li>Keycloak, pretty suitable option, it's a bit more lightweight than CAS (but it's still Java 😒). It supports an LDAP backend. Overall pretty fantastic, used by numerous pros.</li>
<li>ADFS, I want the damn thing to work, lol</li>
<li>Kerberos, well, it might work but not many tools/app support it, so it's a no-go</li>
<li>Authelia, it's perfect, Go-based, supports most of the authentication scheme I want to use, as well as Webauthn, TOTP and mobile push notifications, easy to configure, pretty good community, multiple integrations with proxies and apps</li>
</ul>
</li>
</ul>
<p>I tested a lot of them, so I probably forgot to mention one, don't hesitate to comment!</p>
<h2 id="installation">
    Installation 
    
    <a class="header-link" href="#installation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I choose to use the full <code>docker-compose</code> method with some modifications to suit my setup. The original example uses traefik because it assumes the same network this is fine, but I'm using multiple hosts for apps, so it had to go. Here is what my docker-compose.yml looks like:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">version</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#39;3.1&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">services</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">server</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">image</span>:<span style="color:#bbb"> </span>caddy<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">ports</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">      </span>- <span style="color:#c30">&#39;80:80&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">      </span>- <span style="color:#c30">&#39;443:443&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">volumes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">      </span>- <span style="color:#c30">&#39;./Caddyfile:/etc/caddy/Caddyfile&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">      </span>- <span style="color:#c30">&#39;./root_ca.pem:/etc/caddy/root_ca.pem&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">      </span>- <span style="color:#c30">&#39;./certs:/certs&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">restart</span>:<span style="color:#bbb"> </span>unless-stopped<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">authelia</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">     </span><span style="color:#309;font-weight:bold">image</span>:<span style="color:#bbb"> </span>authelia/authelia<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">     </span><span style="color:#309;font-weight:bold">container_name</span>:<span style="color:#bbb"> </span>authelia<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">     </span><span style="color:#309;font-weight:bold">ports</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">        </span>- <span style="color:#f60">9091</span>:<span style="color:#f60">9091</span>/tcp<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">     </span><span style="color:#309;font-weight:bold">volumes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">        </span>- ./authelia:/config<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">     </span><span style="color:#309;font-weight:bold">restart</span>:<span style="color:#bbb"> </span>unless-stopped<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">     
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">redis</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">image</span>:<span style="color:#bbb"> </span>redis:alpine<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">container_name</span>:<span style="color:#bbb"> </span>redis<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">volumes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#bbb">     </span>- ./redis:/data<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">expose</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#bbb">      </span>- <span style="color:#f60">6379</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">restart</span>:<span style="color:#bbb"> </span>unless-stopped<span style="color:#bbb">
</span></span></span></code></pre></div><p>The <code>Caddyfile</code> is pretty simple, just a simple reverse proxy that registers the domain to my acme server:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#030;font-weight:bold">auth.internal.tugler.fr</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        <span style="color:#069;font-weight:bold">tls</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>                <span style="color:#069;font-weight:bold">ca</span> <span style="color:#c30">https://cert.internal.tugler.fr/acme/acme/directory</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>                <span style="color:#069;font-weight:bold">ca_root</span> <span style="color:#c30">/etc/caddy/root_ca.pem</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>        <span style="color:#069;font-weight:bold">reverse_proxy</span> authelia:<span style="color:#f60">9091</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>}
</span></span></code></pre></div><h2 id="configuration">
    Configuration 
    
    <a class="header-link" href="#configuration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The Authelia config is pretty long but easy to do compared to some other tools I tried.</p>
<p>The first thing to configure is some internal settings to Authelia, like default variables</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">theme</span>:<span style="color:#bbb"> </span>light<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">jwt_secret</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">default_redirection_url</span>:<span style="color:#bbb"> </span>https://internal.tugler.fr/<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">default_2fa_method</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Next is the server, the <code>docker-compose.yml</code> exposes port 9091, so it's the one to be used, I'm using the subdomain <code>auth.internal.tugler.fr</code> so I don't need to change the root path. I also didn't set up ssl directly in there because I have it in the reverse proxy with the acme. The rest is default.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">server</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">host</span>:<span style="color:#bbb"> </span><span style="color:#f60">0.0.0.0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">port</span>:<span style="color:#bbb"> </span><span style="color:#f60">9091</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">path</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">  </span><span style="color:#09f;font-style:italic"># asset_path: /config/assets/</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">read_buffer_size</span>:<span style="color:#bbb"> </span><span style="color:#f60">4096</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">write_buffer_size</span>:<span style="color:#bbb"> </span><span style="color:#f60">4096</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">enable_pprof</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">enable_expvars</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">  
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable_healthcheck</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">tls</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">key</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">certificate</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">client_certificates</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">headers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">csp_template</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>I don't need to save the logs (docker takes care of it anyway usually) so, debug level and that's it.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">log</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">level</span>:<span style="color:#bbb"> </span>debug<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">  </span><span style="color:#09f;font-style:italic"># format: json</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">  </span><span style="color:#09f;font-style:italic"># file_path: /config/authelia.log</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb">  </span><span style="color:#09f;font-style:italic"># keep_stdout: false</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Next is double authentication, I've enabled TOTP and WebAuthn just in case, but I don't actually intend to use it on most apps, maybe on core services like proxmox, but it's not determined yet</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">totp</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">issuer</span>:<span style="color:#bbb"> </span>tugler.fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">algorithm</span>:<span style="color:#bbb"> </span>sha1<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">digits</span>:<span style="color:#bbb"> </span><span style="color:#f60">6</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">period</span>:<span style="color:#bbb"> </span><span style="color:#f60">30</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">skew</span>:<span style="color:#bbb"> </span><span style="color:#f60">1</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">secret_size</span>:<span style="color:#bbb"> </span><span style="color:#f60">32</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">  
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">webauthn</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span>60s<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">display_name</span>:<span style="color:#bbb"> </span>Tugler<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">attestation_conveyance_preference</span>:<span style="color:#bbb"> </span>indirect<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">user_verification</span>:<span style="color:#bbb"> </span>preferred<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">duo_api</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">hostname</span>:<span style="color:#bbb"> </span>api-123456789.example.com<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">integration_key</span>:<span style="color:#bbb"> </span>ABCDEF<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">secret_key</span>:<span style="color:#bbb"> </span>1234567890abcdefghifjkl<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">enable_self_enrollment</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Needs the time to be accurate, apparently 🤷‍♂️</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">ntp</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">address</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;time.cloudflare.com:123&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">version</span>:<span style="color:#bbb"> </span><span style="color:#f60">4</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">max_desync</span>:<span style="color:#bbb"> </span>3s<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable_startup_check</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable_failure</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Next, I need to set up the actual backend, to check users I have a Windows server running just for that (it's about the only thing that works well). The config was pretty simple, there is a pretty good example in the docs to get started. Even if my <code>ldap_bind</code> user has the permissions to modify password, I didn't actually enable it for now.
The password policy also doesn't need to be enabled.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">authentication_backend</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable_reset_password</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">password_reset</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">custom_url</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">refresh_interval</span>:<span style="color:#bbb"> </span>5m<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">ldap</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">implementation</span>:<span style="color:#bbb"> </span>activedirectory<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">url</span>:<span style="color:#bbb"> </span>ldap://ad1.internal.tugler.fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span>5s<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">start_tls</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">tls</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">skip_verify</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">minimum_version</span>:<span style="color:#bbb"> </span>TLS1.2<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">base_dn</span>:<span style="color:#bbb"> </span>dc=tugler,dc=fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">username_attribute</span>:<span style="color:#bbb"> </span>sAMAccountName<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">additional_users_dn</span>:<span style="color:#bbb"> </span>ou=People<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">users_filter</span>:<span style="color:#bbb"> </span>(&amp;({username_attribute}={input})(objectCategory=person)(objectClass=user))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">    
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">additional_groups_dn</span>:<span style="color:#bbb"> </span>ou=Groups<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">groups_filter</span>:<span style="color:#bbb"> </span>(&amp;(member={dn})(objectClass=group))<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">group_name_attribute</span>:<span style="color:#bbb"> </span>cn<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">mail_attribute</span>:<span style="color:#bbb"> </span>mail<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">    </span><span style="color:#09f;font-style:italic"># display_name_attribute: displayName</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">permit_referrals</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">user</span>:<span style="color:#bbb"> </span>cn=ldap_bind,cn=Users,dc=tugler,dc=fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">password</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">password_policy</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">standard</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">enabled</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">min_length</span>:<span style="color:#bbb"> </span><span style="color:#f60">8</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">max_length</span>:<span style="color:#bbb"> </span><span style="color:#f60">0</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">require_uppercase</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">require_lowercase</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">require_number</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">require_special</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">zxcvbn</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">enabled</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">min_score</span>:<span style="color:#bbb"> </span><span style="color:#f60">3</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Next is the access control part, the default is to deny everything, I allow my internal network at <code>10.10.0.0/16</code> and the network of my internal VPN at <code>10.5.0.0/16</code>.
I also added set my app dashboard (<code>internal.tugler.fr</code>) to bypass any auth and only allow users with the group <code>Apps_Paperless_Admin</code> to access <code>paperless.internal.tugler.fr</code> (explained later).
I then proceeded by configuring the Redis cache for the session store and the anti-brute force settings.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">access_control</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">default_policy</span>:<span style="color:#bbb"> </span>deny<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">networks</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>internal<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">networks</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">        </span>- <span style="color:#f60">10.10.0.0</span>/16<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">    </span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>VPN<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">networks</span>:<span style="color:#bbb"> </span><span style="color:#f60">10.5.0.0</span>/16<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">rules</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">    </span>- <span style="color:#309;font-weight:bold">domain</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#39;internal.tugler.fr&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">policy</span>:<span style="color:#bbb"> </span>bypass<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">    </span>- <span style="color:#309;font-weight:bold">domain</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#39;paperless.internal.tugler.fr&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">policy</span>:<span style="color:#bbb"> </span>one_factor<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">subject</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">        </span>- [<span style="color:#c30">&#39;group:Apps_Paperless_Admin&#39;</span>]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">        
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">session</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>authelia_session<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">domain</span>:<span style="color:#bbb"> </span>internal.tugler.fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">same_site</span>:<span style="color:#bbb"> </span>lax<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">secret</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">expiration</span>:<span style="color:#bbb"> </span>4h<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">inactivity</span>:<span style="color:#bbb"> </span>45m<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">remember_me_duration</span>:<span style="color:#bbb"> </span>1M<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#bbb">  
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">redis</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">host</span>:<span style="color:#bbb"> </span>redis<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">port</span>:<span style="color:#bbb"> </span><span style="color:#f60">6379</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">regulation</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">max_retries</span>:<span style="color:#bbb"> </span><span style="color:#f60">3</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">find_time</span>:<span style="color:#bbb"> </span>2m<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">ban_time</span>:<span style="color:#bbb"> </span>5m<span style="color:#bbb">
</span></span></span></code></pre></div><p>Then, I added the credentials for my PostgreSQL database</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">storage</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">encryption_key</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">postgres</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">host</span>:<span style="color:#bbb"> </span>db.internal.tugler.fr<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">port</span>:<span style="color:#bbb"> </span><span style="color:#f60">5432</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">database</span>:<span style="color:#bbb"> </span>authelia<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">username</span>:<span style="color:#bbb"> </span>authelia<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">password</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">timeout</span>:<span style="color:#bbb"> </span>5s<span style="color:#bbb">
</span></span></span></code></pre></div><p>Notifications are something I didn't configure, I'll probably add email notifications in the future, but I don't have a mail server setup right now, so file system notification it is, just to make the thing happy.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">notifier</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">disable_startup_check</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">filesystem</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">filename</span>:<span style="color:#bbb"> </span>/config/notification.txt<span style="color:#bbb">
</span></span></span></code></pre></div><p>The final thing to configure is the OpenID connect provider, for which I basically used the default config. The <code>clients</code> array will be configured on the next part.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">identity_providers</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">oidc</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">hmac_secret</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">issuer_private_key</span>:<span style="color:#bbb"> </span>|<span style="color:#c30;font-style:italic">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#c30;font-style:italic">        -----BEGIN RSA PRIVATE KEY-----
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#c30;font-style:italic">              -----censored-----
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#c30;font-style:italic">        -----END RSA PRIVATE KEY-----</span><span style="color:#bbb">        
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">access_token_lifespan</span>:<span style="color:#bbb"> </span>4h<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">authorize_code_lifespan</span>:<span style="color:#bbb"> </span>5m<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id_token_lifespan</span>:<span style="color:#bbb"> </span>4h<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">refresh_token_lifespan</span>:<span style="color:#bbb"> </span>2h<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">cors</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">endpoints</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">         </span>- authorization<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">         </span>- token<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#bbb">         </span>- revocation<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#bbb">         </span>- introspection<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#bbb">         </span>- userinfo<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">allowed_origins_from_client_redirect_uris</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#bbb">      
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">clients</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span></code></pre></div><p>And that's it.</p>
<h2 id="configuration-1">
    Configuration 
    
    <a class="header-link" href="#configuration-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="proxmox-backup-server">
    Proxmox Backup Server 
    
    <a class="header-link" href="#proxmox-backup-server">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The PBS is pretty easy to configure because it supports OpenID Connect, the only thing to set is the ID, the secret, the redirect URI and the scopes.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#bbb">      </span>- <span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span>pbs<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">description</span>:<span style="color:#bbb"> </span>ProxmoxBackup<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">secret</span>:<span style="color:#bbb"> </span>-----censored-----<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">public</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">false</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">authorization_policy</span>:<span style="color:#bbb"> </span>one_factor<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">scopes</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">          </span>- openid<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">          </span>- groups<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">          </span>- email<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">          </span>- profile<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">redirect_uris</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">          </span>- https://10.10.15.23:8007<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">userinfo_signing_algorithm</span>:<span style="color:#bbb"> </span>RS256<span style="color:#bbb">
</span></span></span></code></pre></div><p>Server side it's the url of Authelia, the ID, secret and scope

    <figure>
        <a target="_blank" href="images/dl_firefox_2022_12_06_12-31-40_Z3VHXrCux4.png" >
            <img alt="enter image description here" src="/homelab-single-sign-on/images/dl_firefox_2022_12_06_12-31-40_Z3VHXrCux4.png" />
        </a>
        
    </figure>

</p>
<p>Here is a demo:
<a target="_blank" href="https://youtu.be/EpZrtVe7h_M">https://youtu.be/EpZrtVe7h_M</a></p>
<h3 id="paperless-ngx">
    Paperless-NGX 
    
    <a class="header-link" href="#paperless-ngx">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Paperless is a bit different since it doesn't support traditional auth methods like OIDC, LDAP, SAML, …. Instead, it supports header auth (See: <a target="_blank" href="https://github.com/paperless-ngx/paperless-ngx/pull/100#issuecomment-1045277482%29">https://github.com/paperless-ngx/paperless-ngx/pull/100#issuecomment-1045277482)</a>.</p>
<p>Basically, you need a reverse proxy that will do the authentication and send user information as a header to the source. Here is my config for caddy:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#030;font-weight:bold">paperless.internal.tugler.fr</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>        <span style="color:#069;font-weight:bold">tls</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>                <span style="color:#069;font-weight:bold">ca</span> <span style="color:#c30">https://cert.internal.tugler.fr/acme/acme/directory</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>                <span style="color:#069;font-weight:bold">ca_root</span> <span style="color:#c30">/etc/caddy/root_ca.pem</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        <span style="color:#069;font-weight:bold">forward_auth</span> http://auth.internal.tugler.fr:<span style="color:#f60">9091</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>                <span style="color:#069;font-weight:bold">uri</span> <span style="color:#c30">/api/verify?rd=https://auth.internal.tugler.fr</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>                <span style="color:#069;font-weight:bold">copy_headers</span> <span style="color:#c30">Remote-User</span> <span style="color:#c30">Remote-Groups</span> <span style="color:#c30">Remote-Name</span> <span style="color:#c30">Remote-Email</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>        <span style="color:#069;font-weight:bold">redir</span> <span style="color:#99f">/404</span>     <span style="color:#c30">/dashboard</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        <span style="color:#069;font-weight:bold">reverse_proxy</span> paperless:<span style="color:#f60">8000</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#030;font-weight:bold">direct.paperless.internal.tugler.fr</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        <span style="color:#069;font-weight:bold">tls</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                <span style="color:#069;font-weight:bold">ca</span> <span style="color:#c30">https://cert.internal.tugler.fr/acme/acme/directory</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>                <span style="color:#069;font-weight:bold">ca_root</span> <span style="color:#c30">/etc/caddy/root_ca.pem</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        <span style="color:#069;font-weight:bold">reverse_proxy</span> paperless:<span style="color:#f60">8000</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>}
</span></span></code></pre></div><p>My case is a bit special because I use the api of paperless for automatically ingesting PDFs from my emails and can't automate the login flow.
Hence, why I have two hosts, one is <code>direct.paperless.internal.tugler.fr</code> which proxies paperless directly (And use the standard paperless auth which have one user specifically for this), and the other is <code>paperless.internal.tugler.fr</code> which goes through Authelia first.</p>
<p>Caddy is configured to send these headers to paperless:</p>
<ul>
<li>Remote-User → Username</li>
<li>Remote-Groups → Groups of said user</li>
<li>Remote-Name → Full name of the user</li>
<li>Remote-Email → Email of the user</li>
</ul>
<p>I also needed to configure environment variables for the docker container. Here is a <strong>very</strong> reduced example:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">paperless</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">image</span>:<span style="color:#bbb"> </span>ghcr.io/paperless-ngx/paperless-ngx:latest<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">restart</span>:<span style="color:#bbb"> </span>unless-stopped<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">environment</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">PAPERLESS_ENABLE_HTTP_REMOTE_USER</span>:<span style="color:#bbb"> </span><span style="color:#f60">1</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">PAPERLESS_LOGOUT_REDIRECT_URL</span>:<span style="color:#bbb"> </span>https://auth.internal.tugler.fr/logout?rq=https://paperless.internal.tugler.fr<span style="color:#bbb">
</span></span></span></code></pre></div><p>Demo video:
<a target="_blank" href="https://youtu.be/9sVzXl-Ttlk">https://youtu.be/9sVzXl-Ttlk</a></p>
<h4 id="update">
    Update 
    
    <a class="header-link" href="#update">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Seems that there are some discussions in the Paperless-NGX repo about multi-user, permission and SSO support:</p>
<ul>
<li><a target="_blank" href="https://github.com/paperless-ngx/paperless-ngx/discussions/295">https://github.com/paperless-ngx/paperless-ngx/discussions/295</a></li>
<li><a target="_blank" href="https://github.com/paperless-ngx/paperless-ngx/discussions/625">https://github.com/paperless-ngx/paperless-ngx/discussions/625</a></li>
<li><a target="_blank" href="https://github.com/paperless-ngx/paperless-ngx/pull/1746">https://github.com/paperless-ngx/paperless-ngx/pull/1746</a>
It would be wonderful to be able to use OIDC directly instead of the in my opinion, hacky remote header way.</li>
</ul>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Overall, I'm very pleased with this setup. It will simplify the login process for all the apps and eliminate the need for multiple user database (for those which don't support LDAP) 😀.</p>
<p>Moreover, it's f-ing cool to have a proper setup in a homelab 😎.
<strong>Self-Host all the things!</strong></p>

 ]]></content:encoded></item><item><title>Running an ACME in my homelab</title><description> &lt;p>Running the open-source step-ca ACME server to provision SSL certificates in my homelab&lt;/p></description><link>https://blog.thestaticturtle.fr/running-an-acme-in-my-homelab/</link><guid>https://blog.thestaticturtle.fr/running-an-acme-in-my-homelab/</guid><category> Homelab</category><category> Security</category><category> Servers</category><category> Tutorials</category><category> Web</category><dc:creator> Samuel</dc:creator><pubDate>Sun, 01 Jan 2023 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/running-an-acme-in-my-homelab/images/cover_hu55e6ff7a9baa69aa8a3a5b4dfc222a0f_99014_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/running-an-acme-in-my-homelab/images/cover_hu55e6ff7a9baa69aa8a3a5b4dfc222a0f_99014_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Running an ACME in my homelab</h1>
<span class="subtitle"><p>Running the open-source step-ca ACME server to provision SSL certificates in my homelab</p></span>
<br>

    <img class="" src='/running-an-acme-in-my-homelab/images/cover_hu55e6ff7a9baa69aa8a3a5b4dfc222a0f_99014_1350x900_fit_q80_box.jpg' alt="Running an ACME in my homelab"/>

<hr>

<p>Firstly, what is an ACME:
ACME means <strong>Automatic Certificate Management Environment</strong> it's a protocol for automating interactions between certificate authorities and servers, allowing the automated deployment of public key infrastructure.</p>
<p>Basically, it's a server that has access to a certificate authority that goes: here is a ssl certificate for domain xxxxxxx.tld to any server that can prove that it owns xxxxxxx.tld</p>
<h2 id="why-ssl-in-the-first-place">
    Why SSL in the first place 
    
    <a class="header-link" href="#why-ssl-in-the-first-place">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>SSL is a way to add security to a service, having a server in a homelab environment shouldn't equal to http is fine. Because it's a useful way to detect unwanted changes (MiTM attacks from hacker friends, for example) and simply a better practice.
Not to mention some apps already uses SSL with no way to disable it (like proxmox) so why not do it properly.</p>
<p>Furthermore, it's f-ing cool 😎</p>
<h2 id="the-why-dont-you-section">
    The &quot;why don't you section&quot; 
    
    <a class="header-link" href="#the-why-dont-you-section">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="why-an-acme-why-not-manually">
    Why an ACME, why not manually? 
    
    <a class="header-link" href="#why-an-acme-why-not-manually">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Well, running a CA manually is more than fine.
Except that when you reach the point where you have +20 ct/vms, you spend all your time provisioning and renewing certificates</p>
<p>Here is a screenshot of my XCA database, pretty full, heh

    <figure>
        <a target="_blank" href="images/dl_xca_2022_11_28_09-21-16_p1RZU9hR7o.png" >
            <img alt="Old TLS certificate list in XCA" src="/running-an-acme-in-my-homelab/images/dl_xca_2022_11_28_09-21-16_p1RZU9hR7o.png" />
        </a>
        
    </figure>

</p>
<p>I do think that XCA is the way for small setups, as it's pretty easy to use.</p>
<p>But for me even certs that have a 70-year validity (which is actually an issue, see next paragraph) it's a pain to manage.</p>
<h3 id="why-dont-you-use-a-long-validity-period">
    Why don't you use a long validity period 
    
    <a class="header-link" href="#why-dont-you-use-a-long-validity-period">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Well, for this the answer is pretty simple, <a target="_blank" href="https://blog.mozilla.org/security/2020/07/09/reducing-tls-certificate-lifespans-to-398-days/">Mozilla decided to reduce the validity period for TLS certs to 398</a> which isn't big compared to 70 years, but it's just enought for you to forget about it and go &quot;Oh. Fuck.&quot; when every cert breaks at the same time.</p>
<h3 id="why-dont-you-use-adcs">
    Why don't you use ADCS 
    
    <a class="header-link" href="#why-dont-you-use-adcs">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Well, windows just plain sucks, I'm sure it's great when you learn all the hacks to make it work, but I want something stable where I won't spend 80h just to get a certificate. Hell, I still need to figure out why my AD just shutdowns randomly from time to time.</p>
<h2 id="first-steps">
    First steps 
    
    <a class="header-link" href="#first-steps">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>There isn't a ton of open-source tools that provides an ACME server, so I had the idea to build one!
I actually read a significant amount of the <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8555.html">RFC</a> and implemented quite a bit when I discovered step-ca.</p>
<blockquote>
<p><code>step-ca</code> is an online Certificate Authority (CA) for secure, automated X.509 and SSH certificate management. It's the server counterpart to <a target="_blank" href="https://smallstep.com/docs/step-cli"><code>step</code>  CLI</a>. It is secured with TLS, and it offers several configurable certificate provisioners, flexible certificate templating, and pluggable database backends to suit a wide variety of contexts and workflows. It employs sane default algorithms and attributes, so you don't have to be a security engineer to use it securely.</p>
</blockquote>
<p>Great that look perfect and as a bonus, they have a superb tutorial for a <a target="_blank" href="https://smallstep.com/blog/build-a-tiny-ca-with-raspberry-pi-yubikey/">small homelab CA</a></p>
<h2 id="acme-setup">
    ACME Setup 
    
    <a class="header-link" href="#acme-setup">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I don't own a Yubikey, I didn't exactly follow the tutorial.
Instead, because step-ca doesn't have an easy way to customize the root and intermediate CA,  I started by creating them in XCA,

    <figure>
        <a target="_blank" href="images/dl_xca_2022_11_28_10-14-00_mOnVfMn6wR.png" >
            <img alt="CA setup in XCA" src="/running-an-acme-in-my-homelab/images/dl_xca_2022_11_28_10-14-00_mOnVfMn6wR.png" />
        </a>
        
    </figure>

</p>
<p>The root and intermediate CA infrastructure is perfect because I actually intend to manage two sites (my lab and the small setup at my parents) and I can use different intermediate CA for both while still having one root.</p>
<p>Next, I run the init process for step-ca with dummy data to create the file structure/default configs</p>
<p>I then opened and replaced the content of:</p>
<ul>
<li>Root CA certificate <code>/root/.step/certs/root_ca.crt</code></li>
<li>Intermediate CA certificated <code>/root/.step/certs/intermediate_ca.crt</code></li>
<li>Root CA key <code>/root/.step/secrets/intermediate_ca_key</code></li>
<li>Intermediate CA key <code>/root/.step/secrets/root_ca_key</code></li>
</ul>
<p>By what I exported from XCA.</p>
<p>I then proceeded by running <code>step ca provisioner add acme --type ACME</code> which added an ACME certificate provisioner with the name <code>acme</code></p>
<p>Next I edited the config at <code>/root/.step/config/ca.json</code> and added the right domains in the <code>dnsNames</code> array,  and also edited the <code>commonName</code> key to something I liked.</p>
<p>The next step was to create a systemd service to start the acme:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-systemd" data-lang="systemd"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">[Unit]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#309">Description</span><span style="color:#555">=</span><span style="color:#c30">step-ca</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#309">After</span><span style="color:#555">=</span><span style="color:#c30">syslog.target network.target</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#069;font-weight:bold">[Service]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#309">User</span><span style="color:#555">=</span><span style="color:#c30">root</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#309">Group</span><span style="color:#555">=</span><span style="color:#c30">root</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#309">ExecStart</span><span style="color:#555">=</span><span style="color:#c30">step-ca</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#309">Type</span><span style="color:#555">=</span><span style="color:#c30">simple</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#309">Restart</span><span style="color:#555">=</span><span style="color:#c30">on-failure</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#309">RestartSec</span><span style="color:#555">=</span><span style="color:#c30">10</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#069;font-weight:bold">[Install]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#309">WantedBy</span><span style="color:#555">=</span><span style="color:#c30">multi-user.target</span>
</span></span></code></pre></div><p>After running:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>root@cert.internal:~# systemctl <span style="color:#366">enable</span> step-ca.service ce
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>root@cert.internal:~# systemctl start step-ca.service
</span></span></code></pre></div><p>I had my server up and running.</p>
<h2 id="acme-client-setup">
    ACME Client setup 
    
    <a class="header-link" href="#acme-client-setup">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So, now that we have an ACME server, we need to actually use it.</p>
<p>First server I updated is my auth server. It uses Caddy as a reverse proxy according to the step-ca docs you need to pass the root ca as an environment variable. However, I would rather not deal with it with docker, so my config looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#030;font-weight:bold">auth.internal.tugler.fr</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>        <span style="color:#069;font-weight:bold">tls</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>                <span style="color:#069;font-weight:bold">ca</span> <span style="color:#c30">https://cert.internal.tugler.fr/acme/acme/directory</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>                <span style="color:#069;font-weight:bold">ca_root</span> <span style="color:#c30">/etc/caddy/root_ca.pem</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>        <span style="color:#069;font-weight:bold">reverse_proxy</span> authelia:<span style="color:#f60">9091</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-docker" data-lang="docker"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>  server:<span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#a00;background-color:#faa"></span>    image: caddy<span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#a00;background-color:#faa"></span>    ports:<span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#a00;background-color:#faa"></span>      - <span style="color:#c30">&#39;80:80&#39;</span><span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#a00;background-color:#faa"></span>      - <span style="color:#c30">&#39;443:443&#39;</span><span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#a00;background-color:#faa"></span>    volumes:<span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span><span style="color:#a00;background-color:#faa"></span>      - <span style="color:#c30">&#39;./Caddyfile:/etc/caddy/Caddyfile&#39;</span><span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span><span style="color:#a00;background-color:#faa"></span>      - <span style="color:#c30">&#39;./root_ca.pem:/etc/caddy/root_ca.pem&#39;</span><span style="color:#a00;background-color:#faa">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span><span style="color:#a00;background-color:#faa"></span>    restart: unless-stopped<span style="color:#a00;background-color:#faa">
</span></span></span></code></pre></div><p>I proceeded by restarting the stack, and it worked the first time:

    <figure>
        <a target="_blank" href="images/dl_firefox_2022_11_28_10-36-38_Q1VhX3qNud.png" >
            <img alt="First valid certificate" src="/running-an-acme-in-my-homelab/images/dl_firefox_2022_11_28_10-36-38_Q1VhX3qNud.png" />
        </a>
        
    </figure>

</p>
<p>Nice 😃</p>
<p>The process for certbot is simple, I just need to add the <code>--directory https://cert.internal.tugler.fr/acme/acme/directory</code> argument to the command to tell it which server to use</p>
<p>After looking at the cert a bit closely, I need to modify the certificate templates for the provider to add the CN and other things since they are empty for now, but it already works with very minimal effort.</p>
<h2 id="adding-the-root-ca-to-clients">
    Adding the root CA to clients 
    
    <a class="header-link" href="#adding-the-root-ca-to-clients">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that we have everything running, we have to tell our clients to actually trust the CA. Pretty easy, you would think? Well, not so, depending on the platform!</p>
<h3 id="windows">
    Windows 
    
    <a class="header-link" href="#windows">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Windows is easy, just import the certificate to the Machine &quot;Certification Authority&quot; store

    <figure>
        <a target="_blank" href="images/dl_mmc_2022_11_28_10-50-02_BeJmJrJ7zA.png" >
            <img alt="My CA in window&#39;s machine store" src="/running-an-acme-in-my-homelab/images/dl_mmc_2022_11_28_10-50-02_BeJmJrJ7zA.png" />
        </a>
        
    </figure>

</p>
<h4 id="chrome">
    Chrome 
    
    <a class="header-link" href="#chrome">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Chrome takes the certificate from the Windows store, so that's done!</p>
<h4 id="firefox">
    Firefox 
    
    <a class="header-link" href="#firefox">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Firefox, as always, is again doing things differently, so you have to go into the options and import it there as well.

    <figure>
        <a target="_blank" href="images/dl_firefox_2022_11_28_10-48-53_W5x5Tm6s0S.png" >
            <img alt="My CA in Firefox" src="/running-an-acme-in-my-homelab/images/dl_firefox_2022_11_28_10-48-53_W5x5Tm6s0S.png" />
        </a>
        
    </figure>


I think that one shitty decision Mozilla made, is the fact that a certificate for &quot;pve1.internal.tugler.fr&quot; will work if you use this url &quot;https://pve1.internal.tugler.fr&quot;, but not for &quot;https://pve1.internal.tugler.fr:8006&quot; it's logic but a pain in the ass. As proxmox for example doesn't allow ports or IPs in a domain for ACME.</p>
<h3 id="servers">
    Servers 
    
    <a class="header-link" href="#servers">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I use some Ansible script to manage my servers, so it was as simple as creating a new role for the server that needs the root ca, coping the file adding the role to my &quot;common&quot; playbook:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#0cf;font-weight:bold">---</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb"></span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>Make sure the folder exists<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">file</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">path</span>:<span style="color:#bbb"> </span>/usr/local/share/ca-certificates<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">state</span>:<span style="color:#bbb"> </span>directory<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb"></span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>Copy the certificate<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">copy</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">src</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;files/tugler_ca_root.pem&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">dest</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;/usr/local/share/ca-certificates/tugler_ca_root.pem.crt&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">register</span>:<span style="color:#bbb"> </span>result<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb"></span>- <span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span>Update CA Trust<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">command</span>:<span style="color:#bbb"> </span>update-ca-certificates<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">when</span>:<span style="color:#bbb"> </span>result is changed<span style="color:#bbb">
</span></span></span></code></pre></div><h3 id="android">
    Android 
    
    <a class="header-link" href="#android">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Android …., what a nightmare, it's so annoying it's actually incredible 😤.
First thing you need to know is that it's not called certificates it's called credentials, good luck finding it in the settings.
Moreover, there are two certificates store, the user store and the system store.
The system store contains the root CA and the user store generally contain user credentials for thing like VPNs.</p>
<p>Importing the certificate to the user store is actually straightforward, download the cert, enter your pin, give it a name, and it's done.</p>
<h4 id="apps">
    Apps 
    
    <a class="header-link" href="#apps">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>However, I want to use apps like paperless ingest, HomeAssistant, Bitwarden, grocy, .....
And guess what, apps want the certificate in the system store.</p>
<p>I'm sure you could do this properly in a managed setup. However, I don't have time to manage that, and my phone is already rooted, so how can we do it?</p>
<p>Turns out, you can either do it manually each time you reboot by moving files around, or you can  use a magisk module that does just that:

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/NVISOsecurity/MagiskTrustUserCerts" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/a178c59ef394439ca00d2c1dd37524ba_hu1e1ffadeb2bebf2f8aac0486d076526e_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/a178c59ef394439ca00d2c1dd37524ba_hu1e1ffadeb2bebf2f8aac0486d076526e_0_0x720_resize_q90_h2_box_3.webp' alt='A Magisk/KernelSU module that automatically adds user certificates to the system root CA store - NVISOsecurity/AlwaysTrustUserCerts'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/NVISOsecurity/MagiskTrustUserCerts">GitHub - NVISOsecurity/AlwaysTrustUserCerts: A Magisk/KernelSU module that automatically adds user certificates to the system root CA store</a>
        
        
            <p>A Magisk/KernelSU module that automatically adds user certificates to the system root CA store - NVISOsecurity/AlwaysTrustUserCerts</p>
        
    </div>
</blockquote>
        
    
</p>
<p>I proceeded by reading the script (always do this before randomly giving root access to something you don't trust), installed it, rebooted and boom here it is:

    <figure>
        <a target="_blank" href="images/dl_Signal_2022_11_28_11-09-54_50LQqc4T8N.png" >
            <img alt="My CA in android&#39;s system store" src="/running-an-acme-in-my-homelab/images/dl_Signal_2022_11_28_11-09-54_50LQqc4T8N.png" />
        </a>
        
    </figure>

</p>
<p>And you know what? It actually worked:

    <figure>
        <a target="_blank" href="images/dl_Signal_2022_11_28_11-12-47_kqNLWlWUSE.png" >
            <img alt="Successful connection of nbz360 to my sonarr instance." src="/running-an-acme-in-my-homelab/images/dl_Signal_2022_11_28_11-12-47_kqNLWlWUSE_huf8b45d3279802c51dc55b7feac76cadf_290169_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h4 id="firefox-1">
    Firefox 
    
    <a class="header-link" href="#firefox-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>You would think it would be easy, right? That Firefox would just work? 🤡</p>
<p>Guess what, Firefox doesn't use system or user provided certificate by default. You have to go into the &quot;Secret Settings&quot; to enable it.</p>
<p>So to make Firefox trust my CA, I have to:</p>
<ul>
<li>Go to Settings &gt; About</li>
<li>Tap the Firefox icon 3 times</li>
<li>Got to Settings &gt; Secret settings</li>
<li>Enable &quot;Use third-party CA certificates&quot;🙄</li>
</ul>
<p>But it works:

    <figure>
        <a target="_blank" href="images/dl_pn_2022_11_28_11-17-28_0F10vNqO14.png" >
            <img alt="Valid certificate in Firefox" src="/running-an-acme-in-my-homelab/images/dl_pn_2022_11_28_11-17-28_0F10vNqO14.png" />
        </a>
        
    </figure>

</p>
<h4 id="chrome-1">
    Chrome 
    
    <a class="header-link" href="#chrome-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Chrome is a system app, right? So, it should just work, right? 🤡
Well, it did until a few years ago, when they started to require certificate transparency.</p>
<p>What the hell is certificate transparency in the first place?
From what I understand, it means that there are public servers that log the certificates changes of public websites when they are issued from well-known CAs. And that chrome queries at least two of these server them to check that it's still valid. Seems pretty useful.</p>
<p>AdGuard has a page explaining it a bit: <a target="_blank" href="https://kb.adguard.com/en/general/https-filtering/https-filtering-known-issues">https://kb.adguard.com/en/general/https-filtering/https-filtering-known-issues</a></p>
<p>Unfortunately, nor my CA, nor my website is public, so what do I do?</p>
<p>It turns out that because it's in the system store, chrome thinks it's a well trusted CA 😶 and wants to verify it, bummer! It can use the one in the user store, but will prioritize the system store.</p>
<p>So, what do you do?
Apparently, there is a way to do it properly use a managed setup (again) and manually telling chrome that this cert doesn't need to be verified
Or, you hide the one in the system store from chrome.</p>
<p>Chrome doesn't need root anyway, so I choose to hide it, in magisk I had to:</p>
<ul>
<li>Enable <code>Zygisk</code></li>
<li>Enable <code>Enforce DenyList</code></li>
<li>Go into the Deny List
<ul>
<li>Enable system apps</li>
<li>Check the chrome app</li>
</ul>
</li>
</ul>
<p>After doing all this and force closing chrome, ignoring the pop-up about installing a CA on first load 🤨, my CA was now valid:

    <figure>
        <a target="_blank" href="images/dl_Signal_2022_11_28_11-29-40_1PSK2ndVkU.png" >
            <img alt="Valid certificate in chrome" src="/running-an-acme-in-my-homelab/images/dl_Signal_2022_11_28_11-29-40_1PSK2ndVkU.png" />
        </a>
        
    </figure>

</p>
<p>This shouldn't be a problem for non-rooted phones anyway because you wouldn't be able to add it to the system store in the first place.</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Well, it was quite an adventure, but now I hopefully don't have to worry about certificates, until the intermediate one expires in 2032 at which point I can just renew and certs will be re-issued automatically. Great 😄</p>
<p>There are two things I'm not happy about, tho:</p>
<ul>
<li>The private key of the CAs live in the file system, it works for a homelab but isn't the best. Ideally when I'll prioritize my budget towards the homelab I'll buy YubiKeys and store the cert on it as it seems pretty easy to do.</li>
<li>The certificate issued by the ACME provider are missing a few fields which isn't a big deal, but it looks pretty easy to add them, so I'll do this in the coming weeks</li>
</ul>

 ]]></content:encoded></item><item><title>Automating my Outlook calendars</title><description> &lt;p>Making my life easier by creating a simple python script to generate Outlook meetings from an Excel spreadsheet.&lt;/p></description><link>https://blog.thestaticturtle.fr/automating-my-outlook-calendars/</link><guid>https://blog.thestaticturtle.fr/automating-my-outlook-calendars/</guid><category> Python</category><category> Tutorials</category><category> Automations</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 01 Dec 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/automating-my-outlook-calendars/images/cover_hud6491c6dff5ba91f7a31194c68f03b98_116355_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/automating-my-outlook-calendars/images/cover_hud6491c6dff5ba91f7a31194c68f03b98_116355_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Automating my Outlook calendars</h1>
<span class="subtitle"><p>Making my life easier by creating a simple python script to generate Outlook meetings from an Excel spreadsheet.</p></span>
<br>

    <img class="" src='/automating-my-outlook-calendars/images/cover_hud6491c6dff5ba91f7a31194c68f03b98_116355_1350x900_fit_q80_box.jpg' alt="Automating my Outlook calendars"/>

<hr>

<p><em>This is something I've done for myself for work on my own time, hence why some parts are vague/blurred!</em></p>
<h2 id="backstory">
    Backstory 
    
    <a class="header-link" href="#backstory">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>A new project started at work, and we needed to make a schedule between two people.
There is 1 to 5 tasks of 10 min that needs to be scheduled each day.</p>
<p>The scheduling is done on an Excel spreadsheet because it's just easier for now. The spreadsheet looks a bit like this:

    <figure>
        <a target="_blank" href="images/dl_EXCEL_2022_11_18_14-58-09_25dyEwwxSR.png" >
            <img alt="enter image description here." src="/automating-my-outlook-calendars/images/dl_EXCEL_2022_11_18_14-58-09_25dyEwwxSR.png" />
        </a>
        
    </figure>

</p>
<p>This works, but it's a bit outdated and since, for the start of the project, I must be present to monitor some things on pretty much each one I wanted something else than a spreadsheet.
Moreover, the spreadsheet is updated frequently which is a pain if you want to set up events in a calendar</p>
<p>At work, we use outlook, outlook has, of course, calendars and a pretty good add-on system and an API.
The idea would be to parse the Excel file and generate meetings in a separate calendar so that I could get notifications and see easier when the next task is happening.</p>
<h2 id="feasibility">
    Feasibility 
    
    <a class="header-link" href="#feasibility">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>First thing is I do is to check if it's even possible:</p>
<p>For Excel, it's pretty simple, python has a very cool module named <code>openpyxl</code> which can read XLSX files.</p>
<p>Outlook was a bit different. It has a pretty good add-ons system and an API you can call from windows, but as always with Microsoft, the docs sucks and are hard to find. Fortunately, simply googling <code>Outlook meeting python</code> yields multiple stack overflow question about this and have some code samples, cool!</p>
<h2 id="lets-start-coding">
    Let's start coding 
    
    <a class="header-link" href="#lets-start-coding">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>First thing I need to say is that this a script that live on my desktop and is not intended to be run automatically nor to be distributed to other people. So code quality went out of the window for this one 😅</p>
<h3 id="excel-file">
    Excel file: 
    
    <a class="header-link" href="#excel-file">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<pre tabindex="0"><code>&gt;&gt;&gt; import openpyxl
&gt;&gt;&gt; workbook = openpyxl.load_workbook(&#34;4BOE9 EEG StudyG1 Schedule Online Shared_Biotrial_15NOV2022_sentABL&amp;LAD.xlsx&#34;)
&gt;&gt;&gt; sheet = workbook.active
&gt;&gt;&gt; header = [cell.value for cell in sheet[&#39;A1:G1&#39;][0]]
&gt;&gt;&gt; header
[&#39;Date&#39;, &#39;Group&#39;, &#39;Visit&#39;, &#39;Time&#39;, &#39;Time Slot&#39;, &#39;Contractor 1&#39;, &#39;Contractor 2&#39;]
</code></pre><p>Perfect, I have the header, now let's get the values</p>
<pre tabindex="0"><code>&gt;&gt;&gt; events = []
&gt;&gt;&gt; row = 2
&gt;&gt;&gt; while True:
...     values = [cell.value for cell in sheet[f&#39;A{row}:G{row}&#39;][0]]
...     values_dict = {}
...     for i in range(len(header)):
...         values_dict[header[i]] = values[i]
...     if values_dict[&#34;Date&#34;] == None:
...         break
...     events.append(values_dict)
...     row += 1
...
&gt;&gt;&gt; for e in event: 
...     print(e)
{&#39;Date&#39;: datetime.datetime(2022, 11, 2, 0, 0), &#39;Group&#39;: &#39;1&#39;, &#39;Visit&#39;: &#39;1&#39;, &#39;Time&#39;: datetime.time(7, 15), &#39;Time Slot&#39;: &#39;07:05-08:35&#39;, &#39;Contractor 1&#39;: &#39;x&#39;, &#39;Contractor 2&#39;: None}
{&#39;Date&#39;: datetime.datetime(2022, 11, 2, 0, 0), &#39;Group&#39;: &#39;1&#39;, &#39;Visit&#39;: &#39;1&#39;, &#39;Time&#39;: datetime.time(8, 15), &#39;Time Slot&#39;: None, &#39;Contractor 1&#39;: &#39;x&#39;, &#39;Contractor 2&#39;: None}
{&#39;Date&#39;: datetime.datetime(2022, 11, 3, 0, 0), &#39;Group&#39;: &#39;1&#39;, &#39;Visit&#39;: &#39;2&#39;, &#39;Time&#39;: datetime.time(7, 15), &#39;Time Slot&#39;: &#39;07:05-08:35&#39;, &#39;Contractor 1&#39;: &#39;x&#39;, &#39;Contractor 2&#39;: None}
{&#39;Date&#39;: datetime.datetime(2022, 11, 3, 0, 0), &#39;Group&#39;: &#39;1&#39;, &#39;Visit&#39;: &#39;2&#39;, &#39;Time&#39;: datetime.time(8, 15), &#39;Time Slot&#39;: None, &#39;Contractor 1&#39;: &#39;x&#39;, &#39;Contractor 2&#39;: None}
{&#39;Date&#39;: datetime.datetime(2022, 11, 4, 0, 0), &#39;Group&#39;: &#39;1&#39;, &#39;Visit&#39;: &#39;3&#39;, &#39;Time&#39;: datetime.time(7, 15), &#39;Time Slot&#39;: &#39;07:05-08:35&#39;, &#39;Contractor 1&#39;: &#39;x&#39;, &#39;Contractor 2&#39;: None}
&lt;more lines&gt;
</code></pre><p>Not the best code but it works, I now have a list of events to add to my Outlook calendar. I can check the <code>'x'</code> in the contractor values to see which one it is.</p>
<h3 id="outlook">
    Outlook 
    
    <a class="header-link" href="#outlook">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I want my meetings to be scheduled 15 min before the task and continue for 15 min after the task.</p>
<p>Let's start by declaring a few things (links at the end):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">win32com.client</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">datetime</span> <span style="color:#069;font-weight:bold">import</span> timedelta, datetime
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">pytz</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>OUTLOOK_MEETINGSTATUS_MEETING <span style="color:#555">=</span> <span style="color:#f60">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>OUTLOOK_MEETINGSTATUS_CANCELED <span style="color:#555">=</span> <span style="color:#f60">5</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>OUTLOOK_MEETINGSTATUS_RECEIVED <span style="color:#555">=</span> <span style="color:#f60">3</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>OUTLOOK_MEETINGSTATUS_RECEIVEDANDCANCELED <span style="color:#555">=</span> <span style="color:#f60">7</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>OUTLOOK_MEETINGSTATUS_NONMEETING <span style="color:#555">=</span> <span style="color:#f60">0</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>OUTLOOK_BUSYSTATUS_BUSY <span style="color:#555">=</span> <span style="color:#f60">2</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>OUTLOOK_BUSYSTATUS_FREE <span style="color:#555">=</span> <span style="color:#f60">0</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>OUTLOOK_BUSYSTATUS_OOO <span style="color:#555">=</span> <span style="color:#f60">3</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>OUTLOOK_BUSYSTATUS_TENATIVE <span style="color:#555">=</span> <span style="color:#f60">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>OUTLOOK_BUSYSTATUS_REMOTE <span style="color:#555">=</span> <span style="color:#f60">4</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>outlook <span style="color:#555">=</span> win32com<span style="color:#555">.</span>client<span style="color:#555">.</span>Dispatch(<span style="color:#c30">&#39;Outlook.Application&#39;</span>)<span style="color:#555">.</span>GetNamespace(<span style="color:#c30">&#39;MAPI&#39;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>calendar_folder <span style="color:#555">=</span> outlook<span style="color:#555">.</span>Folders(<span style="color:#c30">&#39;&lt;my_work_email_here|&#39;</span>)<span style="color:#555">.</span>Folders(<span style="color:#c30">&#39;Calendrier&#39;</span>)<span style="color:#555">.</span>Folders(<span style="color:#c30">&#39;Calendar for the events&#39;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>start_meeting_x_before <span style="color:#555">=</span> <span style="color:#f60">15</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>meeting_duration <span style="color:#555">=</span> start_meeting_x_before <span style="color:#555">+</span> <span style="color:#f60">10</span> <span style="color:#555">+</span> <span style="color:#f60">15</span>
</span></span></code></pre></div><p>Outlook is localized, so <code>Calendrier</code> means <code>Calendars</code> in English and will differ depending on the language, that was fun to find out 🤡.</p>
<p>As I want to be able to run this script without generating duplicates, the first thing to do is to clear all future events:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">for</span> i <span style="color:#000;font-weight:bold">in</span> <span style="color:#366">range</span>(<span style="color:#f60">10</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>    <span style="color:#069;font-weight:bold">for</span> item <span style="color:#000;font-weight:bold">in</span> calendar_folder<span style="color:#555">.</span>Items:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        <span style="color:#069;font-weight:bold">if</span> item<span style="color:#555">.</span>MeetingStatus <span style="color:#555">!=</span> OUTLOOK_MEETINGSTATUS_NONMEETING:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>            <span style="color:#069;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        <span style="color:#069;font-weight:bold">if</span> item<span style="color:#555">.</span>Start <span style="color:#555">&lt;</span> datetime<span style="color:#555">.</span>now()<span style="color:#555">.</span>replace(tzinfo<span style="color:#555">=</span>pytz<span style="color:#555">.</span>UTC):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>            <span style="color:#069;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>        item<span style="color:#555">.</span>Delete()
</span></span></code></pre></div><p>Outlook is a bit flaky, so I repeat the process a few times. Basically, I delete the meeting if it's in the future and does not have any participants.</p>
<p>I then make a list of all meeting that might still be there (because I manually added a participant for example) as I don't want to re-schedule it :</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>already_existing <span style="color:#555">=</span> [item<span style="color:#555">.</span>Start<span style="color:#555">.</span>strftime(<span style="color:#c30">&#39;</span><span style="color:#a00">%d</span><span style="color:#c30">/%m/%Y %H:%M&#39;</span>) <span style="color:#069;font-weight:bold">for</span> item <span style="color:#000;font-weight:bold">in</span> calendar_folder<span style="color:#555">.</span>Items]
</span></span></code></pre></div><p>Then it's just a matter of looping throughout the event array, checking that the event is in the future, checking that it doesn't already exist and creating it</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">for</span> event <span style="color:#000;font-weight:bold">in</span> events:<span style="color:#a00;background-color:#faa">`</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    scheduled_start_time <span style="color:#555">=</span> event[<span style="color:#c30">&#34;Date&#34;</span>] <span style="color:#555">+</span> timedelta(hours<span style="color:#555">=</span>event[<span style="color:#c30">&#34;Time&#34;</span>]<span style="color:#555">.</span>hour, minutes<span style="color:#555">=</span>event[<span style="color:#c30">&#34;Time&#34;</span>]<span style="color:#555">.</span>minute)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    meeting_start_time <span style="color:#555">=</span> scheduled_start_time <span style="color:#555">-</span> timedelta(minutes<span style="color:#555">=</span>start_meeting_x_before)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>    meeting_start_time <span style="color:#555">=</span> meeting_start_time<span style="color:#555">.</span>strftime(<span style="color:#c30">&#39;</span><span style="color:#a00">%d</span><span style="color:#c30">/%m/%Y %H:%M&#39;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>    <span style="color:#069;font-weight:bold">if</span> schedule_start_time <span style="color:#555">&lt;</span> datetime<span style="color:#555">.</span>now():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        <span style="color:#069;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>    <span style="color:#069;font-weight:bold">if</span> meeting_start_time <span style="color:#000;font-weight:bold">in</span> already_existing:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>        <span style="color:#366">print</span>(<span style="color:#c30">&#34;Already existing&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        <span style="color:#069;font-weight:bold">continue</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>            
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>    <span style="color:#366">print</span>(meeting_start_time, event)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>    who <span style="color:#555">=</span> <span style="color:#c30">&#34;UNKNOWN&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>    <span style="color:#069;font-weight:bold">if</span> event[<span style="color:#c30">&#34;Contractor 1&#34;</span>] <span style="color:#555">==</span> <span style="color:#c30">&#34;x&#34;</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        who <span style="color:#555">=</span> <span style="color:#c30">&#34;Alice&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>    <span style="color:#069;font-weight:bold">if</span> event[<span style="color:#c30">&#34;Contractor 2&#34;</span>] <span style="color:#555">==</span> <span style="color:#c30">&#34;x&#34;</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>        who <span style="color:#555">=</span> <span style="color:#c30">&#34;Bob&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>        
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>    meeting <span style="color:#555">=</span> calendar_folder<span style="color:#555">.</span>Items<span style="color:#555">.</span>Add()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>    meeting<span style="color:#555">.</span>MeetingStatus <span style="color:#555">=</span> OUTLOOK_MEETINGSTATUS_NONMEETING
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>    meeting<span style="color:#555">.</span>Start <span style="color:#555">=</span> meeting_start_time
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>    meeting<span style="color:#555">.</span>Duration <span style="color:#555">=</span> meeting_duration
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>    meeting<span style="color:#555">.</span>Subject <span style="color:#555">=</span> <span style="color:#c30">f</span><span style="color:#c30">&#34;</span><span style="color:#a00">{</span>who<span style="color:#a00">}</span><span style="color:#c30"> - </span><span style="color:#a00">{</span>event[<span style="color:#c30">&#39;Group&#39;</span>]<span style="color:#a00">}</span><span style="color:#c30"> </span><span style="color:#a00">{</span>event[<span style="color:#c30">&#39;Visit&#39;</span>]<span style="color:#a00">}</span><span style="color:#c30">&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>    meeting<span style="color:#555">.</span>ResponseRequested <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>    meeting<span style="color:#555">.</span>BusyStatus <span style="color:#555">=</span> OUTLOOK_BUSYSTATUS_FREE
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>    meeting<span style="color:#555">.</span>ReminderSet <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>    meeting<span style="color:#555">.</span>Save()
</span></span></code></pre></div><p>As this is a shared calendar, I wanted to avoid annoying other people by deleting all event and re-creating them. So by default, the events don't have a reminder and are having the Non-Event status, meaning that there are not any participants/organizer.
I also didn't want to be marked as &quot;Busy&quot; in teams for each meeting, so by default it marks me as free
I can manually set these settings if there is a task that I need to monitor.
This make a very nice and organized calendar that I don't have to modify manually:

    <figure>
        <a target="_blank" href="images/dl_OUTLOOK_2022_11_18_15-35-26_4JaqJoyvbG.png" >
            <img alt="enter image description here." src="/automating-my-outlook-calendars/images/dl_OUTLOOK_2022_11_18_15-35-26_4JaqJoyvbG.png" />
        </a>
        
    </figure>

</p>
<h2 id="resources--conclusion">
    Resources &amp; Conclusion 
    
    <a class="header-link" href="#resources--conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Including the time I spent trying to locate a proper documentation for creating the events, I spent maybe two hours on this script. Which isn't that bad considering that it took me 45 min last time I did this manually (which was a week ago)</p>
<p>If you want to do something similar, here are the main things I needed:</p>
<ul>
<li>Appointment Item doc: <a target="_blank" href="https://learn.microsoft.com/en-gb/office/vba/api/outlook.appointmentitem">https://learn.microsoft.com/en-gb/office/vba/api/outlook.appointmentitem</a></li>
<li><a target="_blank" href="https://stackoverflow.com/questions/38899956/python-win32com-get-outlook-event-appointment-meeting-response-status">https://stackoverflow.com/questions/38899956/python-win32com-get-outlook-event-appointment-meeting-response-status</a></li>
<li><a target="_blank" href="https://rmsol.de/2018/06/17/Emails-and-Appointments-with-Outlook-and-Python/">https://rmsol.de/2018/06/17/Emails-and-Appointments-with-Outlook-and-Python/</a></li>
<li><code>outlook.PickFolder()</code> To figure out why the <code>Calendars</code> folder was not found</li>
</ul>
<p>All in all, pretty good.
One amelioration would be to auto create Teams meetings or even auto-detect a change in the meeting and, instead of deleting it, modifying the time.
I might also do something similar to filter my emails with sieve scripts or similar because the outlook filters are rubbish.</p>

 ]]></content:encoded></item><item><title>Adding a C14 connector to my Mikrotik switch</title><description> &lt;p>Recently, I bought some used mikrotik CRS326-24G-2S+RM
These switches are remarkable 1g switches for the price, but they have one flaw: they use an external power adapter
So, I decided to add an IEC C14 connector to it.&lt;/p></description><link>https://blog.thestaticturtle.fr/adding-a-c14-connector-to-my-mikrotik-switch/</link><guid>https://blog.thestaticturtle.fr/adding-a-c14-connector-to-my-mikrotik-switch/</guid><category> Homelab</category><category> Diy</category><category> Electronics</category><category> Tutorials</category><dc:creator> Samuel</dc:creator><pubDate>Sun, 06 Nov 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/adding-a-c14-connector-to-my-mikrotik-switch/images/cover_hu52ab2d9437d356533f67a39c74ee63a2_196677_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/adding-a-c14-connector-to-my-mikrotik-switch/images/cover_hu52ab2d9437d356533f67a39c74ee63a2_196677_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Adding a C14 connector to my Mikrotik switch</h1>
<span class="subtitle"><p>Recently, I bought some used mikrotik CRS326-24G-2S+RM
These switches are remarkable 1g switches for the price, but they have one flaw: they use an external power adapter
So, I decided to add an IEC C14 connector to it.</p></span>
<br>

    <img class="" src='/adding-a-c14-connector-to-my-mikrotik-switch/images/cover_hu52ab2d9437d356533f67a39c74ee63a2_196677_1350x900_fit_q80_box.jfif' alt="Adding a C14 connector to my Mikrotik switch"/>

<hr>

<p>Recently, I bought some used mikrotik CRS326-24G-2S+RM</p>

    
        

        
            








    


<blockquote class="embed embed-mikrotik_com">
    
        
            <a target="_blank" href="https://mikrotik.com/product/CRS326-24G-2SplusRM" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/5345f4a0df70bb889d17a58b16415609_hu3fbe76510ffc8a5ec51bc61ae4b7986e_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/5345f4a0df70bb889d17a58b16415609_hu3fbe76510ffc8a5ec51bc61ae4b7986e_0_0x720_resize_q90_h2_box_3.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://mikrotik.com/product/CRS326-24G-2SplusRM">CRS326-24G-2S&#43;RM | MikroTik</a>
        
        
            <p>24 Gigabit port switch with 2 x SFP+ cages in 1U rackmount case, Dual boot (RouterOS or SwitchOS)</p>
        
    </div>
</blockquote>
        
    

<p>These switches are remarkable 1g switches for the price, but they have one flaw that some sysadmins consider a pretty big one: they use an external power adapter</p>
<h2 id="feasibility">
    Feasibility 
    
    <a class="header-link" href="#feasibility">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Mikrotik also has the same version which is a bit smaller and not rack mounted (the <a target="_blank" href="https://mikrotik.com/product/crs326_24g_2s_in">CRS326-24G-2S+IN</a>) which makes me think the motherboard in the rack mounted version is the same 😅</p>
<p>The back of the switch has an IEC C14 cutout in the metal, it's like one of their engineer knew someone was going to do this 🤣

    <figure>
        <a target="_blank" href="images/dl_DSC00138.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00138_hu63958579d2d2cd16483621ecc416c1b0_12561744_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>The power adapter itself is a 24v 1.2amp one

    <figure>
        <a target="_blank" href="images/dl_DSC00134.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00134_hu2ef28eedc86945777b97bb6eb1ee248c_11339030_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


And the input on the switch specify a range of 10v to 28v, so it should be pretty easy to find a replacement power supply</p>
<p>Adding the IEC C14 connector was a no-brainer really, I only needed the connector and power supply.</p>
<h2 id="parts">
    Parts 
    
    <a class="header-link" href="#parts">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After a quick Amazon search, I choose this adapter, mainly because it matched the original one pretty well and was the fastest delivery one: <a target="_blank" href="https://www.amazon.fr/gp/product/B08P8S4SY6">https://www.amazon.fr/gp/product/B08P8S4SY6</a>

    <figure>
        <a target="_blank" href="images/dl_DSC00129.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00129_hu7e1351f274f4d3b1d8527debfb477ea8_12128210_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


I also bought these connectors: <a target="_blank" href="https://www.amazon.fr/gp/product/B07YTZYSTT">https://www.amazon.fr/gp/product/B07YTZYSTT</a>

    <figure>
        <a target="_blank" href="images/dl_DSC00131.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00131_hu1be4eada8df4299d9ab7de3c19d06f7b_11900116_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="wiring-everything">
    Wiring everything 
    
    <a class="header-link" href="#wiring-everything">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I got some wires from an old 220v cable, soldered them to the C14 connector added some heat shrink and screwed the other side to the power supply input

    <figure>
        <a target="_blank" href="images/dl_DSC00136.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00136_hu2242ecdb36984276694a37d646bd952d_12365910_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00135.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00135_hueb9ad5182c2e233801a0531373f0fe85_12652159_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>As I predicted, 1/3 of the case is empty, leaving more than enough space for the power supply

    <figure>
        <a target="_blank" href="images/dl_DSC00141.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00141_hu22f97439b66b7c80d8769ea0b260384e_12654383_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00143.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00143_hu8e33d6b081462ac06dddcd54e0fda4f9_12082202_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00142.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00142_hu4de148afb642d26df828775b1a001e3b_12135982_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>So, I went on and broke the little tabs, screwed in the C14 connector and connected the power supply:

    <figure>
        <a target="_blank" href="images/dl_DSC00144.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00144_hud445a4b909c5eddad6e2b3e77f2036d7_10810444_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>To actually power the switch, I hesitated between soldering straight to the board and making a wire+connector come out of the case. As the switch is already out of warranty, so I decided to solder directly to the board.</p>
<p>Fortunately, the PCB has some holes that I can use properly instead of soldering to the connector tabs:

    <figure>
        <a target="_blank" href="images/dl_DSC00145.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00145_huafcb00d617aeea235e2fea65c73c170c_13709381_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00146.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00146_hu7c9200076201f751e39a16378085d2f4_13056669_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I also added the earth from the IEC C14 to a screw that touched the chassis:

    <figure>
        <a target="_blank" href="images/dl_DSC00148.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00148_hu410909d307c86a8408a27ab8c223e238_12459761_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00149.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00149_hu879fe46154653a22c50aabf1224dd559_14000735_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>After a quick test to confirm, it still lighted up 🔥 :

    <figure>
        <a target="_blank" href="images/dl_DSC00150.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00150_hu1c7aa67e00fca10b3f7eab38fed9a7bb_12297360_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>I added a little piece of tape between the original connector and the case so that I wouldn't accidentally power it from here:

    <figure>
        <a target="_blank" href="images/dl_DSC00151.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00151_hu31bce3d4d52f7153498b22567a049174_13330393_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00152.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00152_hu8bb01d5da147d0f9fea321b5f62785ab_11630330_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="finishing-up">
    Finishing up 
    
    <a class="header-link" href="#finishing-up">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>And finally, I closed everything and put it back to work on my desk:

    <figure>
        <a target="_blank" href="images/dl_DSC00153.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00153_hu5a3b213d60842ef89bf71c5bd64ac18b_11475414_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00154.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00154_hu962427cc5e6cb9fe9358ad205f01413a_11098555_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00157.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00157_hu017ee261f5fbc320bd0c7ee8918d6fc2_11675217_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_DSC00162.JPG" >
            <img alt="enter image description here" src="/adding-a-c14-connector-to-my-mikrotik-switch/images/dl_DSC00162_hub30e6ed02b1d096b9fcf42c329c2e849_12568299_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>All in all, this is an effortless mod that solves a relatively big issue, and I'll repeat it to the other two switches I own 😄</p>

 ]]></content:encoded></item><item><title>Rendering SVG with KiCad scripting API</title><description><![CDATA[ <p>The adventure of rendering SVGs with KiCad pcbnew python api.</p>
<p>What I wanted to do was pretty simple, at least I thought, I wanted to replicate the comportment of the &quot;Export → SVG&quot; button in KiCad.</p> ]]></description><link>https://blog.thestaticturtle.fr/rendering-svg-with-kicad-scripting-api/</link><guid>https://blog.thestaticturtle.fr/rendering-svg-with-kicad-scripting-api/</guid><category> Python</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 01 Sep 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/rendering-svg-with-kicad-scripting-api/images/cover_hud8276395147fd0cfe3758e17a0da8e59_131510_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/rendering-svg-with-kicad-scripting-api/images/cover_hud8276395147fd0cfe3758e17a0da8e59_131510_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Rendering SVG with KiCad scripting API</h1>
<span class="subtitle"><p>The adventure of rendering SVGs with KiCad pcbnew python api.</p>
<p>What I wanted to do was pretty simple, at least I thought, I wanted to replicate the comportment of the &quot;Export → SVG&quot; button in KiCad.</p></span>
<br>

    <img class="" src='/rendering-svg-with-kicad-scripting-api/images/cover_hud8276395147fd0cfe3758e17a0da8e59_131510_1350x900_fit_q80_box.jpg' alt="Rendering SVG with KiCad scripting API"/>

<hr>
p>While this API is really cool, it also has some issues that I had the pleasure to discover.</p>
<p>What I wanted to do was pretty simple, at least I thought, I wanted to replicate the comportment of the &quot;Export → SVG&quot; button in KiCad. <br>
Pretty easy no? Load the board, create a plot controller change a few options, and we're ready. And this where it went haywire:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">pcbnew</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>filename <span style="color:#555">=</span> <span style="color:#c30">&#34;board.kicad_pcb&#34;</span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>output_dir <span style="color:#555">=</span> <span style="color:#c30">&#34;output/&#34;</span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>board <span style="color:#555">=</span> pcbnew<span style="color:#555">.</span>LoadBoard(filename)    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>plot_controller <span style="color:#555">=</span> pcbnew<span style="color:#555">.</span>PLOT_CONTROLLER(board)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>plot_options <span style="color:#555">=</span> plot_controller<span style="color:#555">.</span>GetPlotOptions()    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>plot_options<span style="color:#555">.</span>SetOutputDirectory(output_dir)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>plot_options<span style="color:#555">.</span>SetPlotFrameRef(<span style="color:#069;font-weight:bold">False</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>plot_options<span style="color:#555">.</span>SetDrillMarksType(pcbnew<span style="color:#555">.</span>PCB_PLOT_PARAMS<span style="color:#555">.</span>FULL_DRILL_SHAPE)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>plot_options<span style="color:#555">.</span>SetSkipPlotNPTH_Pads(<span style="color:#069;font-weight:bold">False</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>plot_options<span style="color:#555">.</span>SetMirror(<span style="color:#069;font-weight:bold">False</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>plot_options<span style="color:#555">.</span>SetFormat(pcbnew<span style="color:#555">.</span>PLOT_FORMAT_SVG)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>plot_options<span style="color:#555">.</span>SetSvgPrecision(<span style="color:#f60">4</span>, <span style="color:#069;font-weight:bold">False</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>plot_options<span style="color:#555">.</span>SetPlotViaOnMaskLayer(<span style="color:#069;font-weight:bold">True</span>)    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>plot_controller<span style="color:#555">.</span>OpenPlotfile(<span style="color:#c30">&#34;mask&#34;</span>, pcbnew<span style="color:#555">.</span>PLOT_FORMAT_SVG, <span style="color:#c30">&#34;Top mask layer&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>plot_controller<span style="color:#555">.</span>SetColorMode(<span style="color:#069;font-weight:bold">True</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>plot_controller<span style="color:#555">.</span>SetLayer(pcbnew<span style="color:#555">.</span>F_Mask)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>plot_controller<span style="color:#555">.</span>PlotLayer()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>plot_controller<span style="color:#555">.</span>ClosePlot() 
</span></span></code></pre></div><p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2022-08-12_02-36-22.png" >
            <img alt="" src="/rendering-svg-with-kicad-scripting-api/images/dl_chrome_2022-08-12_02-36-22.png" />
        </a>
        
    </figure>

</p>
<p>Multiple issues with this:</p>
<ul>
<li>My board has traces in the negative as I like to center around 0 this means the half SVG simply isn't drawn</li>
<li>The render is very tiny for some reason</li>
<li>Hard to notice but despite having <code>SetColorMode</code> set to <code>True</code>, there isn't any colour here</li>
</ul>
<h2 id="first-issue--centering-around-0">
    First issue / Centering around 0 
    
    <a class="header-link" href="#first-issue--centering-around-0">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This, is in retrospect, pretty easy to fix but a nightmare to find.</p>
<p>The pcbnew lib exposes the <code>ComputeBoundingBox</code> function and (luckily) the <code>EDA_RECT</code> class.</p>
<p>To get the center point for the board, we can do:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>pcb_bounding_box <span style="color:#555">=</span> board<span style="color:#555">.</span>ComputeBoundingBox() 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#366">print</span>(<span style="color:#c30">&#34;origin&#34;</span>, pcb_bounding_box<span style="color:#555">.</span>GetOrigin()) 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#366">print</span>(<span style="color:#c30">&#34;height&#34;</span>, pcb_bounding_box<span style="color:#555">.</span>GetHeight()) 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#366">print</span>(<span style="color:#c30">&#34;width&#34;</span>, pcb_bounding_box<span style="color:#555">.</span>GetWidth()) 
</span></span></code></pre></div><p>Then we can enable the use of the auxiliary origin and set it to the origin of the pcb bounding box:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>plot_options<span style="color:#555">.</span>SetUseAuxOrigin(<span style="color:#069;font-weight:bold">True</span>) 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>board<span style="color:#555">.</span>GetDesignSettings()<span style="color:#555">.</span>SetAuxOrigin(pcb_bounding_box<span style="color:#555">.</span>GetOrigin()) 
</span></span></code></pre></div><p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2022-08-12_02-45-46.png" >
            <img alt="" src="/rendering-svg-with-kicad-scripting-api/images/dl_chrome_2022-08-12_02-45-46.png" />
        </a>
        
    </figure>

</p>
<p>First issue solved</p>
<h2 id="second-issue--image-size-the-issue-should-be-elementary-to-solve-we-know-that-the-large-svg-is-caused-by-the-renderer-using-the-page-format-at-least-i-discovered-it-after-a-few-hours-of-reading-the-source-code">
    Second issue / Image size The issue <em>should</em> be elementary to solve. We know that the large SVG is caused by the renderer using the page format (At least I discovered it after a few hours of reading the source code) 
    
    <a class="header-link" href="#second-issue--image-size-the-issue-should-be-elementary-to-solve-we-know-that-the-large-svg-is-caused-by-the-renderer-using-the-page-format-at-least-i-discovered-it-after-a-few-hours-of-reading-the-source-code">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Thanks to the bounding box from the first issue, we know exactly what size the output should, so let's change it.</p>
<p>According to the documentation, the <code>BOARD</code> class has a function called <code>GetPageSettings</code> and I know from reading the source code, that <code>GetPageSettings</code> returns a pointer to a <code>PAGE_INFO</code> class that holds the page size. So one would think that we could do something like this (at least that's what is done in the GUI code when you click &quot;Export → SVG&quot;):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>currpageInfo <span style="color:#555">=</span> board<span style="color:#555">.</span>GetPageSettings()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>currpageInfo<span style="color:#555">.</span>SetWidthMils(<span style="color:#366">int</span>(pcb_bounding_box<span style="color:#555">.</span>GetWidth() <span style="color:#555">/</span> pcbnew<span style="color:#555">.</span>IU_PER_MILS))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>currpageInfo<span style="color:#555">.</span>SetHeightMils(<span style="color:#366">int</span>(pcb_bounding_box<span style="color:#555">.</span>GetHeight() <span style="color:#555">/</span> pcbnew<span style="color:#555">.</span>IU_PER_MILS))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>board<span style="color:#555">.</span>SetPageSettings(currpageInfo) 
</span></span></code></pre></div><p>But no, no, no, the <code>PAGE_INFO</code> class doesn't have a swig proxy in the pcbnew file, so you actually can't modify it. You can use it, but cannot call or access anything inside (the only statements posing an issue are <code>SetWidthMils</code> and <code>SetHeightMils</code>)</p>
<pre tabindex="0"><code>Traceback (most recent call last):    
File &#34;.\main.py&#34;, line 60, in generate  
 k = generator.generate(options.canvas, options.color, **options.options)
File &#34;.\generators\spotify\main.py&#34;, line 281, in generate svgs = pcb2svg.generate_svg_from_gerber_and_drill(kicad_pcb_file.name, theme=color) 
File &#34;.\tools\kicad\pcb2svg.py&#34;, line 281, in generate_svg_from_gerber_and_drill
 currpageInfo.SetWidthMils(int(pcb_bounding_box.GetWidth() / pcbnew.IU_PER_MILS)) AttributeError: &#39;SwigPyObject&#39; object has no attribute &#39;SetWidthMils&#39;
</code></pre><p>So, how do you bypass that? By modifying the exported SVG, of course.</p>
<p>Looking at an SVG generated by the GUI, we can deduce the values that would actually need replacing from the bounding box</p>
<pre tabindex="0"><code>python IU_PER_MM = pcbnew.IU_PER_MILS / 2.54 * 1000 VIEW_BOX_DIVIDER = 100  # Why that value? Wish I knew
new_svg_attributes = {    
    &#34;width&#34;: f&#34;{round(pcb_bounding_box.GetWidth() / IU_PER_MM, 5)}cm&#34;,   
    &#34;height&#34;: f&#34;{round(pcb_bounding_box.GetHeight() / IU_PER_MM, 5)}cm&#34;,   
    &#34;viewBox&#34;: f&#34;0 0 {int(pcb_bounding_box.GetWidth() / VIEW_BOX_DIVIDER)} {int(pcb_bounding_box.GetHeight() / VIEW_BOX_DIVIDER)}&#34;,  
}
</code></pre><p>Thereafter, we can use python XML library to properly edit the attributes of the SVG (i.e., not a brute force <code>.replace</code>)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">xml.etree.ElementTree</span> <span style="color:#069;font-weight:bold">as</span> <span style="color:#0cf;font-weight:bold">xml_et</span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>tree <span style="color:#555">=</span> xml_et<span style="color:#555">.</span>parse(<span style="color:#c30">&#34;output/board-mask.svg&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>root <span style="color:#555">=</span> tree<span style="color:#555">.</span>getroot()    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#069;font-weight:bold">for</span> attr, value <span style="color:#000;font-weight:bold">in</span> new_svg_attributes<span style="color:#555">.</span>items():    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>    root<span style="color:#555">.</span>attrib[attr] <span style="color:#555">=</span> value
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>    svg <span style="color:#555">=</span> xml_et<span style="color:#555">.</span>tostring(root, encoding<span style="color:#555">=</span><span style="color:#c30">&#39;utf8&#39;</span>, method<span style="color:#555">=</span><span style="color:#c30">&#39;xml&#39;</span>) 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>    <span style="color:#366">print</span>(svg)
</span></span></code></pre></div><p>And success we now have a black &amp; white SVG <br>

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-08-12_03-05-37.png" >
            <img alt="" src="/rendering-svg-with-kicad-scripting-api/images/dl_chrome_2022-08-12_03-05-37.png" />
        </a>
        
    </figure>

</p>
<h2 id="third-issue--colours">
    Third issue / Colours 
    
    <a class="header-link" href="#third-issue--colours">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>You would think that using <code>SetColorMode(True)</code> would actually enable colours. Why would that be the case, though? Why would it be easy? <br>
As with the first issue, it's in retrospect a pretty easy fix.</p>
<p>Turns out that you need to tell the thing to use the damn default colour scheme because why a <strong>default</strong> colour scheme would be used by default.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>settings_manager <span style="color:#555">=</span> pcbnew<span style="color:#555">.</span>GetSettingsManager() 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>color_settings <span style="color:#555">=</span> settings_manager<span style="color:#555">.</span>GetColorSettings() 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>plot_options<span style="color:#555">.</span>SetColorSettings(color_settings) 
</span></span></code></pre></div><p>Success we now have ugly colours but at least they are there <br>

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-08-12_03-11-33.png" >
            <img alt="" src="/rendering-svg-with-kicad-scripting-api/images/dl_chrome_2022-08-12_03-11-33.png" />
        </a>
        
    </figure>

</p>
<p>Once again, one would think that we could use the <code>color_settings</code> var to change the colours, but that would be too easy. The <code>COLOR_SETTINGS</code> class doesn't have a swig proxy in the pcbnew, either. Great. <br>
So to modify the colours, we need to pull out the big guns and use <code>.replace</code> on the SVG to get colour that are somewhat OK.</p>
<p>After a bit of poking around in the SVG, the colour are these (didn't bother with the silkscreen for now):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>KICAD_THEME_SEARCH <span style="color:#555">=</span> {    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span> <span style="color:#c30">&#34;top_silkscreen&#34;</span>: <span style="color:#c30">&#34;-----unknown-----&#34;</span>,   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span> <span style="color:#c30">&#34;top_mask&#34;</span>: <span style="color:#c30">&#34;D864FF&#34;</span>,   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span> <span style="color:#c30">&#34;top_layer&#34;</span>: <span style="color:#c30">&#34;C83434&#34;</span>,   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span> <span style="color:#c30">&#34;edge_cuts&#34;</span>: <span style="color:#c30">&#34;D0D2CD&#34;</span>,   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span> <span style="color:#c30">&#34;bottom_layer&#34;</span>: <span style="color:#c30">&#34;C83434&#34;</span>,  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span> <span style="color:#c30">&#34;bottom_mask&#34;</span>: <span style="color:#c30">&#34;D864FF&#34;</span>, <span style="color:#c30">&#34;bottom_silkscreen&#34;</span>: <span style="color:#c30">&#34;-----unknown-----&#34;</span>,   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span> <span style="color:#c30">&#34;drill&#34;</span>: <span style="color:#c30">&#34;ECECEC&#34;</span>,  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span>} 
</span></span></code></pre></div><p>We can then use a bit of code to replace everything:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>top_layer <span style="color:#555">=</span> top_layer\    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;top_silkscreen&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;top_silkscreen&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\   
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;top_mask&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;top_layer&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;top_layer&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;top_mask&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;edge_cuts&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;edge_cuts&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\ 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;drill&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;drill&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>bottom_layer <span style="color:#555">=</span> bottom_layer\    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;bottom_silkscreen&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;bottom_silkscreen&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;bottom_mask&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;bottom_layer&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;bottom_layer&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;bottom_mask&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;edge_cuts&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;edge_cuts&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))\
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span> <span style="color:#555">.</span>replace(KICAD_THEME_SEARCH[<span style="color:#c30">&#34;drill&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>), theme[<span style="color:#c30">&#34;drill&#34;</span>]<span style="color:#555">.</span>encode(<span style="color:#c30">&#34;utf8&#34;</span>))
</span></span></code></pre></div><p>You can note that, as the mask layer isn't actually a mask layer but only a layer, I need to invert <code>top_mask</code> and <code>top_layer</code>. It isn't great but works for now as I wanted to avoid dealing with it any more.</p>
<p>And finally, we have our SVG (rendered with the black ENIG theme) <br>

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-08-12_03-20-09.png" >
            <img alt="" src="/rendering-svg-with-kicad-scripting-api/images/dl_chrome_2022-08-12_03-20-09.png" />
        </a>
        
    </figure>

</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This may appear elementary, but doing things without any proper docs and with APIs that lacks some features can take quite a while. Getting to this state took at somewhere between 8 and 10 hours of reading the documentation, reading the source code and trial &amp; error.</p>
<p>I have great respect for the KiCad devs and while it might look a lot like a rant towards KiCad, <strong>it is not</strong>. <br>
I realize that generating SVG from the python API probably isn't the principal use of the lib. <br>
In my opinion, an API layer should either expose everything that need to access what the API is for or should not exist, no in-between. A few parts don't have a swig proxy and are a pain to get around. I would also have expected a better doc of the api from such a big project. <br>
But then again, KiCad is free and open-source you can't rant at the devs (or anything really). If I had more time, I could implement it and ask to merge it upstream and solve it for everyone, however I do have a life.</p>

 ]]></content:encoded></item><item><title>Too cold? Let's predict things!</title><description> &lt;p>Writing a NodeRed flow to determine when my pool will be at an acceptable temperature using the sun and maths&lt;/p></description><link>https://blog.thestaticturtle.fr/too-cold-lets-predict-things/</link><guid>https://blog.thestaticturtle.fr/too-cold-lets-predict-things/</guid><category> Automation</category><category> Node js</category><category> Diy</category><dc:creator> Samuel</dc:creator><pubDate>Wed, 17 Aug 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/too-cold-lets-predict-things/images/cover.jpg"/><media:content url="https://blog.thestaticturtle.fr/too-cold-lets-predict-things/images/cover.jpg" medium="image"/><content:encoded><![CDATA[ <h1>Too cold? Let&#39;s predict things!</h1>
<span class="subtitle"><p>Writing a NodeRed flow to determine when my pool will be at an acceptable temperature using the sun and maths</p></span>
<br>

    <img class="" src='/too-cold-lets-predict-things/images/cover_huda66575a6f54793a7304bc200bdacb4c_42097_1350x900_fit_q80_box.jpg' alt="Too cold? Let&#39;s predict things!"/>

<hr>

<p>I'm one of the lucky ones to have a pool at my home. But I never know when it's actually hot enough for me to go in, and having the ability to know in advance would be wonderful, since I could plan my day more easily.</p>
<p>We put our pool on the south-east corner of our property (which means it get sunlight for the whole day) and has a &quot;bubble cover&quot; that help them heat up in the sun.</p>
<h2 id="data">
    Data 
    
    <a class="header-link" href="#data">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I already have temperature sensors in my pool, yes sensor<strong>s</strong> one at the top and one at the bottom

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_10-52-14_hJyBS7EQX4.png" >
            <img alt="One day of pool temps" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_10-52-14_hJyBS7EQX4.png" />
        </a>
        
    </figure>

</p>
<p>You can see at 18h45 the pump turning on and mixing the water (which takes about <code>30min</code>).</p>
<p>You can also see that the temps start rising around <code>9h30</code> in a pretty linear way. A bit of digging later, and this time correlates with the sun elevation going over <code>35deg</code>, which is the angle at which it starts covering almost all the pool.

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_11-08-53_NHjJ9bQ715.png" >
            <img alt="Temperature and Sun elevation graph" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_11-08-53_NHjJ9bQ715.png" />
        </a>
        
    </figure>

</p>
<p>After verifying this theory by checking day by day that it actually matches (at least somewhat, I don't want something precise to the second, just to the half hour), I started to work on predicting the temp.</p>
<h1 id="prediction-theory">
    Prediction theory 
    
    <a class="header-link" href="#prediction-theory">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Since the temperature rise is pretty linear, I went with a very basic linear interpolation between two points.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2023-05-01_02-34-36_f9effa2f-3fc1-46b0-a3d6-416aefb3bf65.png" >
            <img alt="" src="/too-cold-lets-predict-things/images/dl_chrome_2023-05-01_02-34-36_f9effa2f-3fc1-46b0-a3d6-416aefb3bf65.png" />
        </a>
        
    </figure>

</p>
<p>This also means that as I'm not using a fancy Ai to determine the temp, I can't do it before I have an acceptable value for the first and second point which I randomly set at 10h30</p>
<p>On the <code>11 of July 2022</code> where I live, the sun attained <code>35deg</code> at approximatively <code>9h28</code> (± 5 min)
At <code>9h28</code>, the top pool sensor reported a temperature of approximatively  <code>25.72degC</code>.
At <code>10h30</code>, the sensor reported a temperature of approximatively <code>26.06degC</code>.</p>
<p>The timestamp for <code>9h29</code> being <code>1657438080s</code> and the timestamp for <code>10h30</code> being <code>1657441800s</code></p>
<p>Let's say I want the temperature at 16h30 (<code>1657452600</code>) we can use the previous defined formula like this:

    <figure>
        <a target="_blank" href="images/dl_chrome_2023-05-01_02-34-45_caeeae8f-87c7-4b67-a0dd-dc3f6be69db4.png" >
            <img alt="" src="/too-cold-lets-predict-things/images/dl_chrome_2023-05-01_02-34-45_caeeae8f-87c7-4b67-a0dd-dc3f6be69db4.png" />
        </a>
        
    </figure>

</p>
<p>Let's check the results, according to the sensor, at <code>16h30</code> the temp was <code>28.49degC</code>, neither a bad result nor a perfect one. A <code>.5degC</code> deviation is a high, but this was approximatively one hour after it started to heat up, it can't be that precise.</p>
<p>Doing the calculation again using the temperature at <code>14h00</code> for <code>x2</code> &amp; <code>y2</code> results in a prediction of <code>28.3degC</code> which I find acceptable for such a crude solution</p>
<h2 id="some-code">
    Some code 
    
    <a class="header-link" href="#some-code">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I wanted to keep things simple (says the man writing code to predict the pool temperature), I used node red, which is a remarkable tool for quickly manipulating data or a stream of data.</p>
<p>In the middle of building the flow, I thought that it might be a good idea to have a floating <code>x2</code> &amp; <code>y2</code>. I decided to use the last value gathered by the sensor (with a limit of <code>17h00</code> to avoid issues with the pump). So, I went ahead and rewrote most of it.</p>
<h3 id="constants">
    Constants 
    
    <a class="header-link" href="#constants">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>First part is an <code>inject</code> node that start at <code>10h00</code> and triggers every <code>10min</code> until <code>17h00</code>, that then feed through a <code>function</code> node that sets some constants:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_12-26-49_JlzaUiDLGL.png" >
            <img alt="Node red flow start" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_12-26-49_JlzaUiDLGL.png" />
        </a>
        
    </figure>

</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>msg.desired_temp <span style="color:#555">=</span> <span style="color:#f60">28.25</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#069;font-weight:bold">var</span> d <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>msg.query_start_time <span style="color:#555">=</span> d.toISOString().substr(<span style="color:#f60">0</span>,<span style="color:#f60">11</span>) <span style="color:#555">+</span> <span style="color:#c30">&#34;00:00:00.000Z&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>msg.query_stop_time <span style="color:#555">=</span> d.toISOString().substr(<span style="color:#f60">0</span>,<span style="color:#f60">11</span>) <span style="color:#555">+</span> <span style="color:#c30">&#34;23:59:59.999Z&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#069;font-weight:bold">return</span> msg;
</span></span></code></pre></div><h3 id="data-query">
    Data query 
    
    <a class="header-link" href="#data-query">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I then use the <code>template</code> node to build the query and the <code>influxdb in</code> node to retrieve the data

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_12-27-04_5I28DNbnTm.png" >
            <img alt="Query nodes" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_12-27-04_5I28DNbnTm.png" />
        </a>
        
    </figure>

</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">SELECT</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">    </span>mean(<span style="color:#c30">&#34;elevation&#34;</span>)<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">AS</span><span style="color:#bbb"> </span><span style="color:#c30">&#34;mean_elevation&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb"></span><span style="color:#069;font-weight:bold">FROM</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">    </span><span style="color:#c30">&#34;homeassistant&#34;</span>.<span style="color:#c30">&#34;autogen&#34;</span>.<span style="color:#c30">&#34;sun.sun&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb"></span><span style="color:#069;font-weight:bold">WHERE</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">    </span>time<span style="color:#bbb"> </span><span style="color:#555">&gt;</span><span style="color:#bbb"> </span><span style="color:#c30">&#39;{{query_start_time}}&#39;</span><span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">AND</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">    </span>time<span style="color:#bbb"> </span><span style="color:#555">&lt;</span><span style="color:#bbb"> </span><span style="color:#c30">&#39;{{query_stop_time}}&#39;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb"></span><span style="color:#069;font-weight:bold">GROUP</span><span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">BY</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">    </span>time(<span style="color:#f60">30</span>s)<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb"></span>FILL(linear)<span style="color:#bbb">
</span></span></span></code></pre></div><p>The query for the temperature is almost the same except for the <code>GROUP BY</code> using <code>time(60s)</code>, you'll see later why this is &quot;needed&quot;.
This query gives a result similar to this (<code>null</code> meaning the sensor didn't have a value at the time):

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_12-33-10_ngpZXcafWz.png" >
            <img alt="Query result." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_12-33-10_ngpZXcafWz.png" />
        </a>
        
    </figure>

</p>
<p>I then immediately enter a <code>function</code> node to get the time when the sun reached 35deg

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_12-35-19_kupyYrtQ0g.png" >
            <img alt="Sun angle node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_12-35-19_kupyYrtQ0g.png" />
        </a>
        
    </figure>


The code loops through all the points and if the elevation is greater than <code>35deg</code> store it in the message object and return it.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">for</span>(<span style="color:#069;font-weight:bold">let</span> point <span style="color:#069;font-weight:bold">of</span> msg.payload) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>    <span style="color:#069;font-weight:bold">if</span> (point.mean_elevation <span style="color:#555">&gt;</span> <span style="color:#f60">35</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>        msg.sun_35deg_localtime <span style="color:#555">=</span> (<span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(<span style="color:#366">Date</span>.parse(point.time))).toLocaleTimeString()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>        msg.sun_35deg_iso       <span style="color:#555">=</span> point.time
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>        msg.sun_35deg_exact     <span style="color:#555">=</span> point.mean_elevation
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>        <span style="color:#069;font-weight:bold">return</span> msg
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span>}
</span></span></code></pre></div><p>I then need to join the two separates messages from the two queries into one. The simplest way to do it I found is to use a <code>join</code> node configured to expect two messages and return one array as the payload, followed by a <code>function</code> node to re-organize it

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_12-27-35_MyaYYK91EZ.png" >
            <img alt="Message aggregation node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_12-27-35_MyaYYK91EZ.png" />
        </a>
        
    </figure>

</p>
<p>And this is why I used two different time groups in the query on array will be bigger than the other. It's very hacky, but works, I'm welcome to a suggestion (using a topic as the key pop's in my mind)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">if</span>(msg.payload[<span style="color:#f60">0</span>].length <span style="color:#555">&gt;</span> msg.payload[<span style="color:#f60">1</span>].length) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>    msg.query_result_sun <span style="color:#555">=</span> msg.payload[<span style="color:#f60">0</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>    msg.query_result_temp <span style="color:#555">=</span> msg.payload[<span style="color:#f60">1</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>} <span style="color:#069;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>    msg.query_result_sun <span style="color:#555">=</span> msg.payload[<span style="color:#f60">1</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>    msg.query_result_temp <span style="color:#555">=</span> msg.payload[<span style="color:#f60">0</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8</span><span><span style="color:#069;font-weight:bold">delete</span> msg.query
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9</span><span><span style="color:#069;font-weight:bold">return</span> msg;
</span></span></code></pre></div><h3 id="data-manipulation">
    Data manipulation 
    
    <a class="header-link" href="#data-manipulation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Originally, I planned to smooth out the temperature to get a more accurate reading, that proved wildly ineffective and decreased the accuracy by approximatively <code>3degC</code>, here is the code for the node anyway (it's a basic low-pass/smoothing function):</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#09f;font-style:italic">// Deepclone
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#09f;font-style:italic"></span>msg.query_result_temp_smooth <span style="color:#555">=</span> JSON.parse(JSON.stringify(msg.query_result_temp))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#069;font-weight:bold">let</span> smoothing <span style="color:#555">=</span> <span style="color:#f60">40</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#069;font-weight:bold">var</span> value <span style="color:#555">=</span> msg.query_result_temp_smooth[<span style="color:#f60">0</span>].temp_top; <span style="color:#09f;font-style:italic">// start with the first input
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#09f;font-style:italic"></span><span style="color:#069;font-weight:bold">for</span> (<span style="color:#069;font-weight:bold">var</span> i<span style="color:#555">=</span><span style="color:#f60">1</span>, len<span style="color:#555">=</span>msg.query_result_temp_smooth.length; i<span style="color:#555">&lt;</span>len; <span style="color:#555">++</span>i){
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>    <span style="color:#069;font-weight:bold">var</span> currentValue <span style="color:#555">=</span> msg.query_result_temp_smooth[i].temp_top;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>    <span style="color:#069;font-weight:bold">if</span>(currentValue) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>        value <span style="color:#555">+=</span> (currentValue <span style="color:#555">-</span> value) <span style="color:#555">/</span> smoothing;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        msg.query_result_temp_smooth[i].temp_top <span style="color:#555">=</span> value;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span> }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#069;font-weight:bold">return</span> msg;
</span></span></code></pre></div><h4 id="extracting-temps">
    Extracting temps 
    
    <a class="header-link" href="#extracting-temps">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Next, the data goes to another <code>function</code> node to extract the temp when the sun is at <code>35deg</code> and the temp either now or at <code>17h00</code>:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-04-46_pu8akFKhJU.png" >
            <img alt="Get temps at time node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-04-46_pu8akFKhJU.png" />
        </a>
        
    </figure>


It's very similar to the first function, it loops through all points and when it meets the condition, stores it in the message object and break out of the loop.
To get the last point between <code>17h00</code> and <code>09h00</code>, I reverse the data and added a simple condition to check the hours (this means that, right now, it will get the value around <code>17h59</code>, not an issue though).</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">let</span> timeSun <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(msg.sun_35deg_iso)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">for</span>(<span style="color:#069;font-weight:bold">let</span> point <span style="color:#069;font-weight:bold">of</span> msg.query_result_temp) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>    <span style="color:#069;font-weight:bold">let</span> time <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(point.time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>    <span style="color:#069;font-weight:bold">if</span> (time <span style="color:#555">&gt;</span> timeSun) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        msg.temp_at_sun <span style="color:#555">=</span> point.temp_top
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        msg.temp_at_sun_time <span style="color:#555">=</span> point.time
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        msg.temp_at_sun_time_local <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(time).toLocaleTimeString()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>        <span style="color:#069;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#069;font-weight:bold">for</span>(<span style="color:#069;font-weight:bold">let</span> point <span style="color:#069;font-weight:bold">of</span> msg.query_result_temp.reverse()) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>    <span style="color:#069;font-weight:bold">let</span> timestamp <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(point.time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>    <span style="color:#069;font-weight:bold">let</span> time <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(<span style="color:#366">Date</span>.parse(point.time))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>    <span style="color:#069;font-weight:bold">if</span> (time.getHours() <span style="color:#555">&lt;=</span> <span style="color:#f60">17</span> <span style="color:#555">&amp;&amp;</span> time.getHours() <span style="color:#555">&gt;</span> <span style="color:#f60">9</span> <span style="color:#555">&amp;&amp;</span> <span style="color:#555">!!</span>point.temp_top) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        msg.temp_at_now_or_1700 <span style="color:#555">=</span> point.temp_top
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>        msg.temp_at_now_or_1700_time <span style="color:#555">=</span> point.time
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>        msg.temp_at_now_or_1700_time_local <span style="color:#555">=</span> time.toLocaleTimeString()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>        <span style="color:#069;font-weight:bold">break</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#069;font-weight:bold">return</span> msg
</span></span></code></pre></div><h4 id="linear-interpolation">
    Linear interpolation 
    
    <a class="header-link" href="#linear-interpolation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h4>
<p>Now that we have our constants for the linear interpolation, we can use another <code>function</code> node, to create the values:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-10-56_huxXN2WGO4.png" >
            <img alt="Linear interpolation node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-10-56_huxXN2WGO4.png" />
        </a>
        
    </figure>


First, we declare some constants like the <code>start</code>, <code>stop</code> and <code>step</code> time for the interpolation. Next, we use the function defined in the last part and create the dataset, assign it to the message object (along with the constants) and return it.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">let</span> interpolation_start <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(msg.query_start_time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#069;font-weight:bold">let</span> interpolation_stop <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(msg.query_stop_time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">let</span> interpolation_step <span style="color:#555">=</span> <span style="color:#f60">1</span> <span style="color:#555">*</span> <span style="color:#f60">60</span> <span style="color:#555">*</span> <span style="color:#f60">1000</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#069;font-weight:bold">let</span> x1 <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(msg.temp_at_sun_time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#069;font-weight:bold">let</span> y1 <span style="color:#555">=</span> msg.temp_at_sun
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#069;font-weight:bold">let</span> x2 <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(msg.temp_at_now_or_1700_time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#069;font-weight:bold">let</span> y2 <span style="color:#555">=</span> msg.temp_at_now_or_1700
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#069;font-weight:bold">let</span> array <span style="color:#555">=</span> []
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#069;font-weight:bold">for</span>(<span style="color:#069;font-weight:bold">let</span> x<span style="color:#555">=</span>interpolation_start; x<span style="color:#555">&lt;</span>interpolation_stop; x<span style="color:#555">+=</span>interpolation_step) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>    array.push({
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>        time<span style="color:#555">:</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(x).toISOString(),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>        interpolation<span style="color:#555">:</span> y1 <span style="color:#555">+</span> ((x <span style="color:#555">-</span> x1) <span style="color:#555">/</span> (x2 <span style="color:#555">-</span> x1)) <span style="color:#555">*</span> (y2 <span style="color:#555">-</span> y1)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>    })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>msg.interpolation_params <span style="color:#555">=</span> { x1, y1, x2, y2 }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>msg.interpolated_temp <span style="color:#555">=</span> array
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#069;font-weight:bold">return</span> msg
</span></span></code></pre></div><p>The next step is to get when the <strong>top of the pool</strong> will be at <code>28.25degC</code>. To accomplish that, I use yet another <code>function</code> node and use the same code as I used to get the temps at a specific time, the only difference being that I search for the time instead of temperature. I also added a little margin to make sure I catch a value.
<strong>NB: I cloud have done the proper thing here and calculate an equation that take a temperature and return a timestamp, but it's 3am 🛏️ and don't really need it in this instance.</strong>

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-15-53_LodgkSZpuM.png" >
            <img alt="Get prediction node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-15-53_LodgkSZpuM.png" />
        </a>
        
    </figure>

</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">for</span>(<span style="color:#069;font-weight:bold">let</span> point <span style="color:#069;font-weight:bold">of</span> msg.interpolated_temp) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">let</span> time <span style="color:#555">=</span> <span style="color:#366">Date</span>.parse(point.time)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    <span style="color:#069;font-weight:bold">if</span> (point.interpolation <span style="color:#555">&gt;</span> msg.desired_temp<span style="color:#555">-</span><span style="color:#f60">0.12</span> <span style="color:#555">&amp;&amp;</span> point.interpolation <span style="color:#555">&lt;</span> msg.desired_temp<span style="color:#555">+</span><span style="color:#f60">0.12</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        msg.desired_temp_time       <span style="color:#555">=</span> time
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>        msg.desired_temp_time_local <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(time).toLocaleTimeString()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>        msg.desired_temp_time_iso   <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">new</span> <span style="color:#366">Date</span>(time).toISOString()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        msg.desired_temp_actual     <span style="color:#555">=</span> point.interpolation
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#069;font-weight:bold">return</span> msg
</span></span></code></pre></div><h3 id="visualization">
    Visualization 
    
    <a class="header-link" href="#visualization">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The last step is visualization, the first thing that happens is that the message goes into a <code>function</code> node that will round all the values to an acceptable decimal point (2 for the values and 6 for the interpolation parameters)

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-19-57_MUZ4pODtOE.png" >
            <img alt="Rounding node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-19-57_MUZ4pODtOE.png" />
        </a>
        
    </figure>

</p>
<p>Then, it goes into multiple <code>function</code> &amp; <code>template</code> nodes that then goes into multiple <code>UI</code> nodes, a <code>debug</code> node and an <code>homeassistant sensor</code> node:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-22-19_NSB8bZHKI0.png" >
            <img alt="UI &amp;amp; Visualization node." src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-22-19_NSB8bZHKI0.png" />
        </a>
        
    </figure>

</p>
<p>And that gives me a nice NodeRed dashboard:

    <figure>
        <a target="_blank" href="images/dl_firefox_2022_07_11_14-32-20_cPdY5jsIB7.png" >
            <img alt="NodeRed dashboard" src="/too-cold-lets-predict-things/images/dl_firefox_2022_07_11_14-32-20_cPdY5jsIB7.png" />
        </a>
        
    </figure>

</p>
<p>And you can see that (at least when I wrote this article) the prediction pretty much spot on. The ha sensor does his job and can be visualized in the dashboard:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-38-05_tCasS23XDr.png" >
            <img alt="Homeassistant entity" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-38-05_tCasS23XDr.png" />
        </a>
        
    </figure>


Don't mind the graph the values are a bit messed up because I put the wrong value in the state during testing and didn't bother clearing the database.

    <figure>
        <a target="_blank" href="images/dl_chrome_2022_07_11_14-38-52_R9TAMnloDu.png" >
            <img alt="Homeassistant entity card" src="/too-cold-lets-predict-things/images/dl_chrome_2022_07_11_14-38-52_R9TAMnloDu.png" />
        </a>
        
    </figure>

</p>
<h2 id="notes">
    Notes 
    
    <a class="header-link" href="#notes">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This was done as an experiment and for fun when I noticed that the temperature curve was somewhat linear, not as an actual statistical analysis that I would actually rely on.
I'm not a maths head either, there are probably better ways to do this.</p>
<p>To get the actual temperature of the pool, I would need to do some more maths to average the top and bottom sensor, I, however, do no care 😅.</p>
<p>Things I learned:</p>
<ul>
<li>InfluxQL queries (very briefly), it feels quite a bit like SQL, so it was pretty easy to write a query that worked</li>
<li>Did some &quot;maths&quot; again 😅</li>
<li>Learn how to use the NodeRed dashboards for the first time. Conclusion is not fan, but works very well for quickly displaying data</li>
<li>Read quite a bit about data prediction while searching things for this project.</li>
<li>NodeRed still rocks 🚀</li>
</ul>
<p>If you want the flow, email me (see my portfolio), I'll send it to you. If you would like to chat about it or other projects, please join my discord (<a target="_blank" href="https://discord.com/invite/z8bwtdE">https://discord.com/invite/z8bwtdE</a>) or directly comment below 😄</p>

 ]]></content:encoded></item><item><title>Keeping it cool, automating a window</title><description> &lt;p>Let's make a completely overkill project to automate my skylight and integrate it with HomeAssistant.&lt;/p></description><link>https://blog.thestaticturtle.fr/keeping-it-cool-automating-a-window/</link><guid>https://blog.thestaticturtle.fr/keeping-it-cool-automating-a-window/</guid><category> Diy</category><category> Iot</category><category> 3 d</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 01 Aug 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/keeping-it-cool-automating-a-window/images/cover_huce776bc630cdf4244ef895ce36210232_128982_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/keeping-it-cool-automating-a-window/images/cover_huce776bc630cdf4244ef895ce36210232_128982_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Keeping it cool, automating a window</h1>
<span class="subtitle"><p>Let's make a completely overkill project to automate my skylight and integrate it with HomeAssistant.</p></span>
<br>

    <img class="" src='/keeping-it-cool-automating-a-window/images/cover_huce776bc630cdf4244ef895ce36210232_128982_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Keeping it cool, automating a window"/>

<hr>

<p>Summer is here, and it's bringing the heat with it.
My bedroom is on the 1st floor and the 1st floor is where it gets really hot, some people can sleep when it's hot, I, however, can't.</p>
<p>Fortunately, I have a sleep schedule that some people call &quot;messed up&quot; however I prefer &quot;non-traditional&quot;, so I can open my window at 23h and close it when I go to sleep at 02h30 and have my room relatively cool.</p>
<p>However, by the time I wake up, it's already much too hot, and I don't like it. I could leave the window open, but light wakes me up easily, and I want to avoid being woken up by the sun at 5h30</p>
<p>So instead of remembering to open the window every day, my maker mind went to the obvious solution, which was: let's start yet another completely overkill project to automate this thing to open and close automatically.</p>
<h2 id="moving-the-window">
    Moving the window 
    
    <a class="header-link" href="#moving-the-window">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I said, my room is on the first floor and our house have a slanted roof, so I have a velux (skylight) that pivots on the top, like that:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_185438.jpg" >
            <img alt="Closed skylight" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_185438_hudbb30a4df9ccdb06cc304af5d6a48b27_62015_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_185508.jpg" >
            <img alt="Opened skylight" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_185508_hu9f187d579a8817591d03770d250de0fa_73091_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>So, I foolishly bought a 40 cm linear actuator, thinking that I could fit it so that I would have a big range, that was a stupid idea, the damn thing wouldn't fit at all. Therefore, I returned it and bought a 20 cm one, which barely fit.

    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_185541.jpg" >
            <img alt="Linear actuator mounting points" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_185541_hucef387f6b20b71481a53744df2e4bf56_81658_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="controlling-the-motor">
    Controlling the motor 
    
    <a class="header-link" href="#controlling-the-motor">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After this was installed, I started working on the controller, this was a bit tricky.  The linear actuator has end stops that prevent it from going too far in or out. This is very useful to avoid the system breaking itself.
But for me, it's even more useful because it helps to determine the state of the window. If I supply power to the actuator input, and it doesn't move, it means that it's either open or closed, which gives us this table:</p>
<table>
<thead>
<tr>
<th>Motor supply</th>
<th>Motor running</th>
<th>Outcome</th>
</tr>
</thead>
<tbody>
<tr>
<td>+12v</td>
<td>No</td>
<td>Open</td>
</tr>
<tr>
<td>+12v</td>
<td>Yes</td>
<td>Opening</td>
</tr>
<tr>
<td>-12v</td>
<td>Yes</td>
<td>Closing</td>
</tr>
<tr>
<td>-12v</td>
<td>No</td>
<td>Closed</td>
</tr>
<tr>
<td></td>
<td>No</td>
<td>Stopped</td>
</tr>
<tr>
<td></td>
<td>Yes</td>
<td>Problem 🤣</td>
</tr>
</tbody>
</table>
<p>Now, how can we detect if the motor is running? The first thing I thought of was to use a current sensor and as a bonus, I would be able to test if the motor stalls, there are, however, multiple issues with this:</p>
<ul>
<li>Couldn't get the damn sensor to appear on an i2c scanner  😠 (probably broken, I ordered another one)</li>
<li>The actuator I got is using a lead screw and apparently have 750 N of force, which would rip out the drywall screws way before any current sensing would detect it</li>
</ul>
<p>The next thing I thought of is to use a similar approach to my previous controller, using two optocouplers to detect in which direction is the motor running.
The issue with that is that, as I said before, the actuator doesn't have connection directly to the motor, but only via a switch. So, I opened the <strong>very</strong> greasy gearbox and spliced the wires to get a direct connection to the motors.

    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_193233.jpg" >
            <img alt="Motor gearbox" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_193233_hu3216372dc38629d3698325ead8a49d10_176162_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


As I didn't have a 3 wire cable big and flexible enough, I used two speaker wires and added multiple layers of heat shrink. While the heath shrink was still hot, I pressed on the cover to create something that look good enough and has some strain relief. It's not the prettiest thing, but it works and will barely ever be seen.</p>
<h2 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As this project is very similar to my previously done garage door controller, I based a lot of the design from it.</p>
<h3 id="powering-the-board">
    Powering the board 
    
    <a class="header-link" href="#powering-the-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I went with a basic circuit with a DC-DC convert to reduce 12v to 5v. Also added some decoupling caps, a power LED and a jumper for disconnecting the 12v input

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-18-57.png" >
            <img alt="Power regulation circuit." src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-18-57.png" />
        </a>
        
    </figure>

</p>
<h3 id="rotation-direction-of-the-motor">
    Rotation direction of the motor 
    
    <a class="header-link" href="#rotation-direction-of-the-motor">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Next is the detection of the motor direction, same design as my garage door controller, one optocoupler works when opening and the other one when closing.

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-07-18.png" >
            <img alt="Motor optocouplers detection circuit" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-07-18.png" />
        </a>
        
    </figure>

</p>
<h3 id="powering-the-motor">
    Powering the motor 
    
    <a class="header-link" href="#powering-the-motor">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I could have used a traditional H-Bridge (like a L293) but as I don't need speed control, so I simply used two SPDT relays that I had on hand. Depending which one I turn on, the motor will spin in the according direction.

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-16-33.png" >
            <img alt="Relay motor controller" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-16-33.png" />
        </a>
        
    </figure>

</p>
<h3 id="user-buttons">
    User buttons 
    
    <a class="header-link" href="#user-buttons">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>To still be able to use this thing without a phone, I added 3 buttons to control the motor (open, close, and stop). I also tied the stop button to GPIO0, which is the boot pin for the esp8266.

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-22-18.png" >
            <img alt="Input buttons pull-up" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-22-18.png" />
        </a>
        
    </figure>

</p>
<h3 id="mcu">
    MCU 
    
    <a class="header-link" href="#mcu">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>For the mcu, I choose a simple esp8266 with both a d1 mini footprint and an esp12 footprint (with an ams1117 to get 3.3v):

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-25-53.png" >
            <img alt="MCU" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-25-53.png" />
        </a>
        
    </figure>

</p>
<h2 id="prototype">
    Prototype 
    
    <a class="header-link" href="#prototype">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Subsequently, I did a very crude prototype:

    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_212716.jpg" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_212716_hu35ffbdd0e0f60be688775999cb32f29a_305049_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="pcb">
    PCB 
    
    <a class="header-link" href="#pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>And everything worked, so I designed a pcb:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-42-17.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-42-17.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-43-15.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-43-15.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_21-49-50.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_21-49-50.png" />
        </a>
        
    </figure>

</p>
<p>Once again, PCBWay stepped in and offered to manufacture the PCB</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_PCBway1_1-1.png" >
            <img alt="" src="/keeping-it-cool-automating-a-window/images/dl_PCBway1_1-1.png" />
        </a>
        
    </figure>

</p>
<h2 id="soldering-the-board">
    Soldering the board 
    
    <a class="header-link" href="#soldering-the-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After waiting for one week, I received the PCBs</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20220715_125832.jpg" >
            <img alt="Top side of the PCB" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220715_125832_hu094fac23f43f59548993c6b6d27533dd_1377945_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_IMG_20220715_125842.jpg" >
            <img alt="Bottom side of the PCB" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220715_125842_huad5909a9b6037e298b0a6d4f5cfe2381_1056811_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>After looking at the PCBs, they were pretty good, nothing wrong with the 5 boards.</p>
<p>A quick test later, I concluded that the board worked just fine.</p>
<h2 id="software">
    Software 
    
    <a class="header-link" href="#software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As this isn't a massive project, I did all the software development under the Arduino IDE.</p>
<p>In short, the esp8266 connects to my home Wi-Fi network and then connect to the MQTT server running on my home automation VM. It then listens for command and periodically reports the window status, over the serial port, mqtt.</p>
<p>Almost all the functions use two structs, one for reading the state of the window and another one for sending commands:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c++" data-lang="c++"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">enum</span> <span style="color:#0a8;font-weight:bold">window_status_t</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>  DS_OPEN           <span style="color:#555">=</span> <span style="color:#f60">0</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>  DS_OPEN_PARTIAL   <span style="color:#555">=</span> <span style="color:#f60">1</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>  DS_OPENING        <span style="color:#555">=</span> <span style="color:#f60">2</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>  DS_CLOSING        <span style="color:#555">=</span> <span style="color:#f60">3</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>  DS_CLOSED         <span style="color:#555">=</span> <span style="color:#f60">4</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>  DS_ERROR_UNKNOWN       <span style="color:#555">=</span> <span style="color:#f60">251</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>  DS_ERROR_OVERCURRENT   <span style="color:#555">=</span> <span style="color:#f60">252</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>  DS_ERROR_HANDLE_CLOSED <span style="color:#555">=</span> <span style="color:#f60">254</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>  DS_UNKNOWN <span style="color:#555">=</span> <span style="color:#f60">255</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>};
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#069;font-weight:bold">enum</span> <span style="color:#0a8;font-weight:bold">move_direction_t</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>  MV_STOP   <span style="color:#555">=</span> <span style="color:#f60">0</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>  MV_OPEN   <span style="color:#555">=</span> <span style="color:#f60">1</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>  MV_CLOSE  <span style="color:#555">=</span> <span style="color:#f60">2</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>};
</span></span></code></pre></div><p>As you can see in the <code>window_status_t</code> struct, I added some edge cases that I actually didn't implement in hardware (such as the handle sensor or the overcurrent sensor). I might add the handle sensor in the future because that motor will rip that thing out without any question if it starts opening with it closed.</p>
<p>The main function responsible to move the window is this one:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c++" data-lang="c++"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">bool</span> <span style="color:#c0f">move_window</span>(move_direction_t direction) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>  <span style="color:#069;font-weight:bold">switch</span>(direction) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>    <span style="color:#069;font-weight:bold">case</span> <span style="color:#99f">MV_STOP</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>      digitalWrite(pin_motor_open, RELAY_DISABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>      digitalWrite(pin_motor_close, RELAY_DISABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>      <span style="color:#069;font-weight:bold">break</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>    <span style="color:#069;font-weight:bold">case</span> <span style="color:#99f">MV_OPEN</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>      digitalWrite(pin_motor_open, RELAY_ENABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>      digitalWrite(pin_motor_close, RELAY_DISABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>      <span style="color:#069;font-weight:bold">break</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>    <span style="color:#069;font-weight:bold">case</span> <span style="color:#99f">MV_CLOSE</span>:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>      digitalWrite(pin_motor_open, RELAY_DISABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>      digitalWrite(pin_motor_close, RELAY_ENABLE_VALUE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>      <span style="color:#069;font-weight:bold">break</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>  }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>  delay(<span style="color:#f60">150</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>  window_status_t status <span style="color:#555">=</span> getWindowStatus();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>  <span style="color:#069;font-weight:bold">if</span>(direction <span style="color:#555">==</span> MV_STOP  <span style="color:#555">&amp;&amp;</span> (status <span style="color:#555">==</span> DS_OPEN_PARTIAL <span style="color:#555">||</span> status <span style="color:#555">==</span> DS_OPEN <span style="color:#555">||</span> status <span style="color:#555">==</span> DS_CLOSED)) { <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">true</span>; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>  <span style="color:#069;font-weight:bold">if</span>(direction <span style="color:#555">==</span> MV_OPEN  <span style="color:#555">&amp;&amp;</span> (status <span style="color:#555">==</span> DS_OPEN    <span style="color:#555">||</span> status <span style="color:#555">==</span> DS_OPENING)) { <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">true</span>; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>  <span style="color:#069;font-weight:bold">if</span>(direction <span style="color:#555">==</span> MV_CLOSE <span style="color:#555">&amp;&amp;</span> (status <span style="color:#555">==</span> DS_CLOSED  <span style="color:#555">||</span> status <span style="color:#555">==</span> DS_CLOSING)) { <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">true</span>; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>  Serial.println(<span style="color:#c30">&#34;Error:&#34;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>  send_status_update_all(getWindowStatus());
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>  
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>  <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">false</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>}
</span></span></code></pre></div><p>It works like this:</p>
<ul>
<li>First, we set the motor direction.</li>
<li>Then, it waits a moment.</li>
<li>Check that's either going to correct direction or that it's in the state we want it to be in</li>
<li>Exits</li>
</ul>
<h2 id="home-assistant-integration">
    Home assistant integration 
    
    <a class="header-link" href="#home-assistant-integration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>You can see here are the message the board transmits via mqtt:

    <figure>
        <a target="_blank" href="images/dl_MQTT_Explorer_2022-07-03_22-06-04.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_MQTT_Explorer_2022-07-03_22-06-04.png" />
        </a>
        
    </figure>

</p>
<p>I then hit the same issue that I had with my garage door controller. HomeAssistant doesn't support a mqtt cover with a status &quot;partially_opened&quot;. So, I need to transmit two window_status messages the first one is &quot;normal&quot; and the second topic treats the <code>partially_opened</code> state as <code>open</code></p>
<p>You can also see a fan sub-topic, on the pcb, I added a fan output in case I want to add a fan in the future.</p>
<p>Here is the HomeAssistant configuration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>mqtt<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">device_class</span>:<span style="color:#bbb"> </span>shade<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">unique_id</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;velux_chambre_samuel&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">name</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;Velux samuel&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">availability_topic</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;iot/upstairs/window/velux_samuel/available&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">payload_available</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;yes&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">payload_not_available</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;no&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">command_topic</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;iot/upstairs/window/velux_samuel/command&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">payload_open</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;open&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">payload_close</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;close&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">payload_stop</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;stop&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_topic</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;iot/upstairs/window/velux_samuel/window_status&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_closed</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;closed&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_open</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;open&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_closing</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;closing&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_opening</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;opening&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">state_stopped</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;open_partial&#34;</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>The result is a nice entity showing up on the dashboard:

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-03_22-03-23.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-03_22-03-23.png" />
        </a>
        
    </figure>

</p>
<p>Now let's do some automation, I couldn't do an automation easily with the UI, so I went full YAML mode and wrote this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">alias</span>:<span style="color:#bbb"> </span>Velux<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">description</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">trigger</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>time<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">at</span>:<span style="color:#bbb"> </span>input_datetime.time_open_velux_samuel<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;open&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>time<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">at</span>:<span style="color:#bbb"> </span>input_datetime.time_close_velux_samuel<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">id</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;close&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">condition</span>:<span style="color:#bbb"> </span>[]<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#bbb"></span><span style="color:#309;font-weight:bold">action</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">service</span>:<span style="color:#bbb"> </span><span style="color:#c30">&#34;cover.{{  trigger.id  }}_cover&#34;</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">entity_id</span>:<span style="color:#bbb"> </span>cover.velux_samuel<span style="color:#bbb">
</span></span></span></code></pre></div><p>Basically, it has two triggers with a corresponding ID, I can then use this ID as a template in the action part of the automation.</p>
<p>After adding the open/close time helpers and adding everything to the UI, I have a very nice interface to control the thing

    <figure>
        <a target="_blank" href="images/dl_chrome_2022-07-06_19-15-18.png" >
            <img alt="enter image description here" src="/keeping-it-cool-automating-a-window/images/dl_chrome_2022-07-06_19-15-18.png" />
        </a>
        
    </figure>


I could have used the sun rise time as a way to schedule the closing time of the window, but I prefer being able to manually set it.</p>
<h2 id="case--finished-installation">
    Case &amp; finished installation 
    
    <a class="header-link" href="#case--finished-installation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="user-inputs">
    User inputs 
    
    <a class="header-link" href="#user-inputs">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I spent a bit of time on SolidWorks designing a button box that's not completely stupid to stick on my wall:

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2022-07-03_22-11-39.png" >
            <img alt="3D model" src="/keeping-it-cool-automating-a-window/images/dl_SLDWORKS_2022-07-03_22-11-39.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2022-07-03_22-12-17.png" >
            <img alt="Transparent 3D model" src="/keeping-it-cool-automating-a-window/images/dl_SLDWORKS_2022-07-03_22-12-17.png" />
        </a>
        
    </figure>



    <figure>
        <a target="_blank" href="images/dl_IMG_20220703_220839.jpg" >
            <img alt="3D Print with the buttons" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220703_220839.jpg" />
        </a>
        
    </figure>

</p>
<p>This was originally intended to match the angle of the roof and put the button parallel to the wall, but instead it fitted just perfectly to the top of my mosquito net, so I put it there instead.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20220726_183818.jpg" >
            <img alt="Buttons mounted" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220726_183818_hu5596d358e1cb3e64d298b29e52030a80_3230961_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h3 id="controller">
    Controller 
    
    <a class="header-link" href="#controller">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I then spent more time designing a case for the controller:

    <figure>
        <a target="_blank" href="images/dl_SLDWORKS_2022-07-31_18-57-14.png" >
            <img alt="Controller case &#43; PCB" src="/keeping-it-cool-automating-a-window/images/dl_SLDWORKS_2022-07-31_18-57-14_hu44b42cfddae4703d28ea4e190d6d5b6e_446981_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


As you can see, I also added light pipes to the cover. These light pipes will be filled with hot glue and painted black (when I'll get the time to do it).</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20220726_183754.jpg" >
            <img alt="Controller mounted on the wall" src="/keeping-it-cool-automating-a-window/images/dl_IMG_20220726_183754_hu8fdbb855df0ae8c081bc5e00ef7b3136_3333718_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="demo">
    Demo 
    
    <a class="header-link" href="#demo">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>And here is a little demo video of the whole system:

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/qMDlRPEto4c"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    
</p>
<h2 id="noteworthy-stuff">
    Noteworthy stuff 
    
    <a class="header-link" href="#noteworthy-stuff">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Here are some things that I learned the hard way on this project:</p>
<ul>
<li>Not touching that grease again</li>
<li>esp8266 modules really don't like 20v DC</li>
<li>Use proper drywall screws or wall anchors to secure things</li>
<li>When you sleep opening / closing speed isn't critical, so it's better to have a slower &amp; quieter motor than a high-speed &amp; noisy motor</li>
</ul>
<h2 id="links">
    Links 
    
    <a class="header-link" href="#links">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Arduino's code / 3D files / schematic / PCB:  <a target="_blank" href="https://github.com/TheStaticTurtle/TiltWindowController">https://github.com/TheStaticTurtle/TiltWindowController</a></p>
<p>Again, thanks to  <a target="_blank" href="https://www.pcbway.com/">pcbway</a> for letting me try out their PCB manufacturing for this project, and if you would like to chat about it or other projects, please join my discord here:</p>
<p><a target="_blank" href="https://discord.com/invite/z8bwtdE">https://discord.com/invite/z8bwtdE</a></p>
<p>
    <figure>
        <a target="_blank" href="images/dl_PCBway1_1-2.png" >
            <img alt="" src="/keeping-it-cool-automating-a-window/images/dl_PCBway1_1-2.png" />
        </a>
        
    </figure>

</p>

 ]]></content:encoded></item><item><title>Let's talk about WireGuard</title><description> &lt;p>Let's see what WireGuard, the fastest and newest VPN protocol, can offer us.&lt;/p></description><link>https://blog.thestaticturtle.fr/lets-talk-about-wireguard/</link><guid>https://blog.thestaticturtle.fr/lets-talk-about-wireguard/</guid><category> Servers</category><category> Security</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 01 Aug 2022 12:00:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/lets-talk-about-wireguard/images/cover_hu7338de9a70aaf0d0357e314e6056b455_38983_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/lets-talk-about-wireguard/images/cover_hu7338de9a70aaf0d0357e314e6056b455_38983_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Let&#39;s talk about WireGuard</h1>
<span class="subtitle"><p>Let's see what WireGuard, the fastest and newest VPN protocol, can offer us.</p></span>
<br>

    <img class="" src='/lets-talk-about-wireguard/images/cover_hu7338de9a70aaf0d0357e314e6056b455_38983_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Let&#39;s talk about WireGuard"/>

<hr>

<h1 id="what-is-wireguard">
    What is WireGuard 
    
    <a class="header-link" href="#what-is-wireguard">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Wikipedia defines WireGuard as:</p>
<blockquote>
<p>A communication protocol and free and open-source software that implements encrypted virtual private networks, and was designed with the goals of ease of use, high speed performance, and low attack surface.</p>
</blockquote>
<p>But what does it actually mean?</p>
<h2 id="first-a-bit-of-history">
    First a bit of history 
    
    <a class="header-link" href="#first-a-bit-of-history">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>PPTP (Point-to-Point Tunnelling Protocol) is one of the oldest VPN protocols in existence, developed in the mid-1990s by some Microsoft engineers. Built into Windows 95, this PPTP was specifically designed for dial-up connections. But with time, the encryption of PPTP was quickly obsolete, compromising its security.</p>
<p>That's just to say VPN are old.</p>
<p>Although IKEv2 is not as popular as other protocols, it is present in many mobile VPNs. It offers one key advantage: being able to reconnect during a loss of connection, as well as during a change of network.</p>
<p>OpenVPN, is a protocol which has quickly become one of the most widely used protocols. Apart from being open source, OpenVPN is also one of the most secure protocols.
In addition to providing strong encryption, OpenVPN is also available on a majority of platforms: Windows, macOS, Linux, Android, iOS, routers, and more.</p>
<h2 id="why-use-it">
    Why use it 
    
    <a class="header-link" href="#why-use-it">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that we know a bit more about other VPN techs, why should care about WireGuard ?</p>
<p>Most VPN solutions in existence today were designed a long time ago. They are therefore particularly slow and sophisticated. This is where WireGuard comes in.</p>
<p>WireGuard prioritizes security and simplicity. It started in 2017 when Jason Donenfeld, a security researcher, needed a stealth traffic tunnelling solution that could be used during penetration testing missions.</p>
<p>WireGuard uses a selection of modern, thoroughly tested and peer-reviewed encryption algorithms. Specifically, WireGuard uses ChaCha20 for symmetric encryption, with Poly1305 for message authentication. This combination is intended to be more efficient than AES on processor architectures that do not have cryptographic hardware acceleration.
It also includes built-in protection against key spoofing, denial of service, and replay attacks.</p>
<p>The protocol is also discreet, since it does not respond to packets from peers that it does not recognize. Therefore, a network scan will not reveal that WireGuard is running on a machine. Also, the connection between peers, goes silent when there is no data exchange.</p>
<p>On Linux, WireGuard works exclusively within the kernel. Because of this, its performance is much better than that of OpenVPN.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2022_05_10_08-57-37_GxnS6BMpXP.png" >
            <img alt="enter image description here" src="/lets-talk-about-wireguard/images/dl_chrome_2022_05_10_08-57-37_GxnS6BMpXP.png" />
        </a>
        
    </figure>

</p>
<p>At equal hardware, it achieves almost quadruple the speed than that of OpenVPN while still keeping the CPU free for other task</p>
<p>However, WireGuard implementations for Android, iOS, macOS, OpenBSD, and Windows are written in the Go programming language while their functionally remains identical, non-Linux WireGuard implementations work in user space. They also don't enjoy the same performance as the kernel implementation. That said, they still manage to match or exceed OpenVPN in a majority of cases.</p>
<p>WireGuard is so light compared to other heavy protocols that it has successfully been implemented on IoT devices such as the ESP32:

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/ciniml/WireGuard-ESP32-Arduino" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/5228cce19a86823d420d4b13def53bab_hucbae47ddc1f94321502e82397d3c25da_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/5228cce19a86823d420d4b13def53bab_hucbae47ddc1f94321502e82397d3c25da_0_0x720_resize_q90_h2_box_3.webp' alt='WireGuard implementation for ESP32 Arduino. Contribute to ciniml/WireGuard-ESP32-Arduino development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/ciniml/WireGuard-ESP32-Arduino">GitHub - ciniml/WireGuard-ESP32-Arduino: WireGuard implementation for ESP32 Arduino</a>
        
        
            <p>WireGuard implementation for ESP32 Arduino. Contribute to ciniml/WireGuard-ESP32-Arduino development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    
</p>
<h1 id="manual-setup">
    Manual setup 
    
    <a class="header-link" href="#manual-setup">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<h2 id="terminology">
    Terminology 
    
    <a class="header-link" href="#terminology">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Creating a WireGuard tunnel is extremely easy, but first there are some terms that need to be clarified for ease of understanding.
Each WireGuard configuration contains</p>
<ul>
<li><strong>One interface</strong>: an <code>[Interface]</code> is the configuration of the actual system interface, it contains a public / private keypair, the address of the client and sometimes the DNS</li>
<li><strong>One or more peer</strong>: A <code>[Peer]</code> can be seen as a link to another machine, it can be one of two things:
<ul>
<li>Client configuration for the server</li>
<li>Server information for the client</li>
</ul>
</li>
</ul>
<p>For this example, I'll make a tunnel between two VPS that I rent.</p>
<h2 id="preparation">
    Preparation 
    
    <a class="header-link" href="#preparation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The first thing I did is to generate keypairs for the server and the client:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>root@vps-main:~# wg genkey | tee /dev/stderr | wg pubkey
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#033">cCwKXFdKHSWl13MrmCa14xgi0nVKOM9C84iu7c4V9Xo</span><span style="color:#555">=</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>gjElwjk3JrkD/ygAdU9kDorNlAB0ipzMLerqzoyPeGw<span style="color:#555">=</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>root@vps-f2ab7591:~# wg genkey | tee /dev/stderr | wg pubkey
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>AGfj1lm/qshnn5BNJ3e7psD6YRoGXdPF/DEvvNbDuHg<span style="color:#555">=</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>7vWlRieSG02iZ60XU8fRsr/jaLKFZdlX1APA0lpBWTA<span style="color:#555">=</span>
</span></span></code></pre></div><h2 id="server">
    Server 
    
    <a class="header-link" href="#server">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that that is done, I created the server on <code>vps-a</code>, to do that, I edited the file <code> /etc/wireguard/wg0.conf</code> and wrote this:</p>
<pre tabindex="0"><code class="language-wireguard" data-lang="wireguard">[Interface]
Address = 192.168.99.1/24
ListenPort = 56789
PrivateKey = cCwKXFdKHSWl13MrmCa14xgi0nVKOM9C84iu7c4V9Xo=

[Peer]
PublicKey = 7vWlRieSG02iZ60XU8fRsr/jaLKFZdlX1APA0lpBWTA=
AllowedIPs = 192.168.99.2/32
</code></pre><p>Let's explain each line:</p>
<ul>
<li><code>[Interface]</code> Indicate the start of an interface definition</li>
<li><code>Address</code> Set the IP address of the server in the VPN, in this case  <code>192.168.99.1</code>. It also sets the range to <code>/24</code></li>
<li><code>ListenPort</code> Set the server port to <code>56789</code></li>
<li><code>PrivateKey</code> Sets the VPN private key
<br></li>
<li><code>[Peer]</code> As this is a server, peer in this context means client</li>
<li><code>PublicKey</code> Sets the public key of that client</li>
<li><code>AllowedIPs</code> Sets the IP addresses that the client is allowed to use, in this case only <code>192.168.99.2</code></li>
</ul>
<p>After that, it's just a matter of enabling the interface and starting the service with the help of <code>wg-quick</code>:</p>
<pre tabindex="0"><code>root@vps-main:~# systemctl enable wg-quick@wg0.service
root@vps-main:~# systemctl restart wg-quick@wg0.service
</code></pre><p>Then we can run the <code>wg</code> command to see if everything was successfully setup:</p>
<pre tabindex="0"><code>root@vps-main:~# wg show wg0
interface: wg0
  public key: gjElwjk3JrkD/ygAdU9kDorNlAB0ipzMLerqzoyPeGw=
  private key: (hidden)
  listening port: 56789

peer: 7vWlRieSG02iZ60XU8fRsr/jaLKFZdlX1APA0lpBWTA=
  allowed ips: 192.168.99.2/32
</code></pre><h2 id="client">
    Client 
    
    <a class="header-link" href="#client">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Now that we have a server we need a client, thankfully it's pretty much the same thing. Here is the config:</p>
<pre tabindex="0"><code class="language-wireguard" data-lang="wireguard">[Interface]
Address = 192.168.99.2/24
ListenPort = 56789
PrivateKey = AGfj1lm/qshnn5BNJ3e7psD6YRoGXdPF/DEvvNbDuHg=

[Peer]
PublicKey = gjElwjk3JrkD/ygAdU9kDorNlAB0ipzMLerqzoyPeGw=
AllowedIPs = 192.168.99.0/24
Endpoint = my-server.tld:56789
</code></pre><p>Let's explain each line:</p>
<ul>
<li><code>[Interface]</code> Indicate the start of an interface definition</li>
<li><code>Address</code> Set the IP address of the client in the VPN, in this case  <code>192.168.99.2</code>. It also sets the range to <code>/24</code></li>
<li><code>ListenPort</code> Set the server port to <code>56789</code></li>
<li><code>PrivateKey</code> Sets the client private key
<br></li>
<li><code>[Peer]</code> As this is a client, peer in this context means the server</li>
<li><code>PublicKey</code> Sets the public key of the server</li>
<li><code>AllowedIPs</code> Sets the IP addresses that the client is allowed to use, in this case the whole <code>192.168.99.0</code> subnet can be accessed. <em>Using <code>0.0.0.0/0</code> would allow WireGuard to route everything thought the VPN</em></li>
<li><code>Endpoint</code> Sets the address of the server</li>
</ul>
<p>Same as before, after that, it's just a matter of enabling the interface and starting the service with the help of <code>wg-quick</code>:</p>
<pre tabindex="0"><code>root@vps-f2ab7591:~# systemctl enable wg-quick@wg0.service
root@vps-f2ab7591:~# systemctl restart wg-quick@wg0.service
</code></pre><p>Then we can run the <code>wg</code> command to see if everything was successfully setup:</p>
<pre tabindex="0"><code>root@vps-f2ab7591:~# wg show wg0
interface: wg0
  public key: 7vWlRieSG02iZ60XU8fRsr/jaLKFZdlX1APA0lpBWTA=
  private key: (hidden)
  listening port: 56789

peer: gjElwjk3JrkD/ygAdU9kDorNlAB0ipzMLerqzoyPeGw=
  endpoint: my-server.tld:56789
  allowed ips: 192.168.99.0/24
</code></pre><h2 id="testing">
    Testing 
    
    <a class="header-link" href="#testing">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This output threw me off a bit because I usually see a bit more infos, thankfully after a quick test, I could verify that the tunnel was working</p>
<pre tabindex="0"><code>root@vps-f2ab7591:~# ping 192.168.99.1
PING 192.168.99.1 (192.168.99.1) 56(84) bytes of data.
64 bytes from 192.168.99.1: icmp_seq=1 ttl=64 time=30.2 ms
</code></pre><pre tabindex="0"><code>root@vps-main:~# ping 192.168.99.2
PING 192.168.99.2 (192.168.99.2) 56(84) bytes of data.
64 bytes from 192.168.99.2: icmp_seq=1 ttl=64 time=30.1 ms
</code></pre><p>And after that data have flown a bit, I saw the information I was accustomed to:</p>
<pre tabindex="0"><code>root@vps-f2ab7591:~# wg show wg0
interface: wg0
  public key: 7vWlRieSG02iZ60XU8fRsr/jaLKFZdlX1APA0lpBWTA=
  private key: (hidden)
  listening port: 56789

peer: gjElwjk3JrkD/ygAdU9kDorNlAB0ipzMLerqzoyPeGw=
  endpoint: my-server.tld:56789
  allowed ips: 192.168.99.0/24
  latest handshake: 26 seconds ago
  transfer: 604 B received, 692 B sent
</code></pre><p>Meaning, everything worked perfectly !!</p>
<p>To test things I bit further, I did a quick iperf3 test:</p>
<pre tabindex="0"><code>root@vps-f2ab7591:~# iperf3 -c 192.168.99.1
Connecting to host 192.168.99.1, port 5201
[  5] local 192.168.99.2 port 40414 connected to 192.168.99.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  12.4 MBytes   104 Mbits/sec   31    645 KBytes
[  5]   1.00-2.00   sec  11.8 MBytes  98.8 Mbits/sec    0    729 KBytes
[  5]   2.00-3.00   sec  10.8 MBytes  90.5 Mbits/sec    0    792 KBytes
[  5]   3.00-4.00   sec  11.8 MBytes  98.8 Mbits/sec    0    838 KBytes
[  5]   4.00-5.00   sec  11.0 MBytes  92.6 Mbits/sec    3    632 KBytes
[  5]   5.00-6.00   sec  10.8 MBytes  90.5 Mbits/sec    0    708 KBytes
[  5]   6.00-7.00   sec  11.8 MBytes  98.8 Mbits/sec    0    765 KBytes
[  5]   7.00-8.00   sec  10.8 MBytes  90.5 Mbits/sec    0    806 KBytes
[  5]   8.00-9.00   sec  10.8 MBytes  90.5 Mbits/sec    0    831 KBytes
[  5]   9.00-10.00  sec  11.8 MBytes  98.8 Mbits/sec    0    846 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   114 MBytes  95.4 Mbits/sec   34             sender
[  5]   0.00-10.00  sec   112 MBytes  93.9 Mbits/sec                  receiver
</code></pre><p>Which also work perfectly <strong>(NOTE: My VPS only has a 1Gbit connection, this is not the result of WireGuard)</strong></p>
<p>All in all, it took me 45 min to set up and test the VPN <strong>while writing this article</strong>, I could probably do it in 15 min which wouldn't be the case for any other tech, particularly OpenVPN</p>
<h1 id="mikrotik">
    Mikrotik 
    
    <a class="header-link" href="#mikrotik">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>As I'm currently building my homelab, after a lot of consideration, I went with MikroTik, partly because they don't cost a limb, partly because their latest version supports WireGuard
I'll have more details of my homelab in an upcoming post but for now my core router is a RB450Gx4 which is not the final router and have some severe limitation, but I got it for 20eur so who cares for testing things out.</p>
<h2 id="preparation-1">
    Preparation 
    
    <a class="header-link" href="#preparation-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>MikroTik stupid Winbox UI doesn't auto generate WireGuard keys so, as before, we need to generate a keypair on our machine:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>$ wg genkey | tee /dev/stderr | wg pubkey
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#033">0IIT9e0zO338ljbWiMONZbxfmVLiHnJs3ZlxzsrxZ0A</span><span style="color:#555">=</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>u8+M7kLlXJkPvcRsPx7+8rfgUOPV6G9Xu7hubuklpA8<span style="color:#555">=</span>
</span></span></code></pre></div><h2 id="server-1">
    Server 
    
    <a class="header-link" href="#server-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Setting up the interface is super easy, just click the <code>+</code> button, paste the private key and click <code>Ok</code>:

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="Interface creation on mikrotik" src="/lets-talk-about-wireguard/images/dl_image.png" />
        </a>
        
    </figure>

</p>
<p>Next we need to add a peer, to do that, I went to the <code>Peers</code> tab, clicked the <code>+</code> button, pasted the client public key, set his IP address and clicked OK

    <figure>
        <a target="_blank" href="images/dl_image-1.png" >
            <img alt="Peer creation" src="/lets-talk-about-wireguard/images/dl_image-1.png" />
        </a>
        
    </figure>

</p>
<h2 id="testing-1">
    Testing 
    
    <a class="header-link" href="#testing-1">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>To test things, I added the configuration to the WireGuard client on my phone:

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="enter image description here" src="/lets-talk-about-wireguard/images/dl_image-2.png" />
        </a>
        
    </figure>


As you can see, it successfully connected to the VPN and after a quick battle with firewall rules I was able to use the mikrotik app:

    <figure>
        <a target="_blank" href="images/dl_image-3.png" >
            <img alt="enter image description here" src="/lets-talk-about-wireguard/images/dl_image-3.png" />
        </a>
        
    </figure>

</p>
<h1 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>WireGuard is in my opinion better than most VPNs, it offers excellent security while still having blazing fast speeds. It's so lightweight that it can be run on IoT devices, while powerful enough to link server together at their max speed (at least for me).<br>
It's also very easy to configure, which is more than I can say for OpenVPN or IPsec/IKEv2, for example.</p>
<p>I'm very pleased with this, and I can say that unless I need the VPN to run over ssl, it will be a really easy choice to make.</p>
<h1 id="links">
    Links 
    
    <a class="header-link" href="#links">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<ul>
<li><a target="_blank" href="https://www.wireguard.com/performance/">https://www.wireguard.com/performance/</a></li>
<li><a target="_blank" href="https://www.wireguardconfig.com/">https://www.wireguardconfig.com/</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/WireGuard">https://en.wikipedia.org/wiki/WireGuard</a></li>
<li><a target="_blank" href="https://wiki.archlinux.org/title/WireGuard">https://wiki.archlinux.org/title/WireGuard</a></li>
</ul>

 ]]></content:encoded></item><item><title>TurtleAuth 2.1: A design for everyday use</title><description> &lt;p>Making a updated and sturdier second version of TurtleAuth, my stm32 based GNUK and U2F tokens&lt;/p></description><link>https://blog.thestaticturtle.fr/turtleauth-2-1-a-design-for-everyday-use/</link><guid>https://blog.thestaticturtle.fr/turtleauth-2-1-a-design-for-everyday-use/</guid><category> Diy</category><category> Security</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 11 Apr 2022 06:43:28 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/turtleauth-2-1-a-design-for-everyday-use/images/cover_hu556a646094eaa55fd17811183688b96c_129879_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/turtleauth-2-1-a-design-for-everyday-use/images/cover_hu556a646094eaa55fd17811183688b96c_129879_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>TurtleAuth 2.1: A design for everyday use</h1>
<span class="subtitle"><p>Making a updated and sturdier second version of TurtleAuth, my stm32 based GNUK and U2F tokens</p></span>
<br>

    <img class="" src='/turtleauth-2-1-a-design-for-everyday-use/images/cover_hu556a646094eaa55fd17811183688b96c_129879_1350x900_fit_q80_box.jpg' alt="TurtleAuth 2.1: A design for everyday use"/>

<hr>

<h2 id="context">
    Context 
    
    <a class="header-link" href="#context">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>A while back, I created the TurtleAuth, this was a simple stm32f103 + USB connector on a stick. This allowed me to flash firmware like gnuk or stm32-uf2-token, which provided me with a hardware gpg key and uf2 key respectively.</p>
<p>However, I recently observed some issue that needed a 2nd revision and here we are.</p>
<h2 id="why">
    Why 
    
    <a class="header-link" href="#why">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So the issues are entirely with the hardware. I didn't have a single issue with the firmware part of the project (but I'll be touching it a bit later since I needed to somehow get the private key off the original keys)</p>
<p>After more than a year of using these keys daily, they started to show theirs weaknesses. In my <a target="_blank" href="https://blog.thestaticturtle.fr/lets-make-a-diy-gpg-usb-key/">previous post</a>, I stated that “the big mistake that I made, the USB is too far in and interferes with the board”. This became a problem after a few months because the USB broke, and I need to resolder it constantly.</p>
<p>With the old revision, I had put all the passives on the bottom side, this works great for a bench PCB which don't experience daily abuse. But on a keychain things are different, and I had to replace multiple capacitors / resistors that broke off.</p>
<p>These issues lead me to make the TurtleAuth 2.1 more resilient and fixed the previous issues.</p>
<h3 id="whats-new">
    What's new 
    
    <a class="header-link" href="#whats-new">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>In this revision, I fixed the USB connector positioning and also reduced the component size.</p>
<p>For this reason (and also as a challenge to myself), I decided to go with 0402 components and I decided to try to put everything on the top side</p>
<p>Unless you live in a cave, you've heard of the component shortage. I'm one of the few who still has some stm32f103 in stock, so I didn't even bother to find out what I could replace it with because:</p>
<pre><code>A) I don't want to pay for an IC right now
B) I already have one.
</code></pre>
<p>This, however, poses a tiny issue that the stm32f103c8t6 is more than 2/3 the size of the width of the PCB.</p>
<p>As another challenge to myself, It thought, it would be cool to use the PCB itself as a case, a bit like the <a target="_blank" href="https://www.kickstarter.com/projects/conorpatrick/solo-v2-safety-net-against-phishing">Solo V2</a>.</p>
<p>Everything else is pretty much the same as the old version</p>
<h2 id="how">
    How 
    
    <a class="header-link" href="#how">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As I said before, the schematic isn't that different from the old version. A few things changed:</p>
<ul>
<li>I removed the BOOT1 selector as it was annoying to change and useless</li>
<li>Used a bigger selector footprint for BOOT0</li>
<li>Removed the headers for the old “top” PCB</li>
<li>Changed the crystals (8MHz and 32k) to smaller ones</li>
<li>Changed all the passives to 0402</li>
<li>Added test points for the debug port</li>
</ul>
<p>This gives a pretty good and small schematic for the new version:

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_image.png" />
        </a>
        
    </figure>

Schematic of the TurtleAuth 2.1</p>
<h3 id="pcb">
    PCB 
    
    <a class="header-link" href="#pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I took an interesting approach with the PCBs. Instead of make a 3d printed case that would add to the overall size of the key, I decided to layer 3 PCBs on top of each other, allowing for a very sturdy construction

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_image.png" />
        </a>
        
    </figure>


The first PCB holds every component, as I'm making a case out of PCBs, I needed to put everything on the top side, this made for some very, intriguing design requirements.</p>
<p>First, since I needed a spacer between the top PCB and the bottom PCB, I had to leave around 1.5 mm of clearance on each side. I spaced some copper pads evenly on the bottom and middle PCB to allow putting a bit of solder in them and merge them together.</p>
<p>Next, with an area of around 22 mm by 10 mm, I had no choice but to go with 0402 components where possible. All passives are 0402 and the LEDs are 0603. I had to break a few best practice guidelines to make it fit, but in the end it all worked out.</p>
<h3 id="soldering">
    Soldering 
    
    <a class="header-link" href="#soldering">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Once again, <a target="_blank" href="https://www.pcbway.com/">PCBway</a> stepped in and volunteered to sponsor the boards, thanks to them, I was able to make boards that need a pretty fine precision

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_image-2.png" />
        </a>
        
    </figure>


After a week of shipping, I received 10 of each PCB and promptly inspected them for any defects. I was very pleased to see nothing wrong with every board, even the middle PCB which has a narrower part on one border (0.8 mm) were all intact

    <figure>
        <a target="_blank" href="images/dl_IMG_20220408_175919.jpg" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220408_175919_hu99321190b7c0f4dc823eb4aa3e6482b9_320828_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


The board were truly amazing, and I'm very pleased with <a target="_blank" href="https://www.pcbway.com/">pcbway</a> PCBs.</p>
<p>Now for the painful part of soldering the components. Now, I'm a bit of an idiot and didn't think to take a photo before soldering the second PCB in place

    <figure>
        <a target="_blank" href="images/dl_IMG_20220408_205617.jpg" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220408_205617_hua15fec7cab9ad5bab28790e06d0539be_270223_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


Turns out, I somehow messed up my order and accidentally ordered, 10050 sized capacitors, so I had to use 0603 caps for the oscillators and the TTP223. Not a big deal because smd caps footprint are generally a bit oversized, so I could solder them without an issue.</p>
<p>The real issue was the chipresistor shortage. It turns out that the 3 resistors I need for the USB part of the stm32 were delayed until September 2022 (so around 5 months later).</p>
<p>Nevertheless, I had some 0603 of these in stock, and it barely fits on the footprint, I was afraid that it wouldn't make a good contact, but it works without an itch.</p>
<p>The component were pretty easy to solder overall.</p>
<p>I then had to program the chip because the second PCB obstruct the debug port, these challenges will be detailed in the next part.</p>
<p>Soldering the middle PCB was actually pretty easy, I just had to flow solder in each pinhole and check that the solder went all the way down, the secret here is using a ton a flux. After I was sure that I soldered everything, I prepped it for the top PCB by tinning the exposed parts.</p>
<p>I then did the same for the top PCB and soldered them together, and that was the tricky part. I started by soldering the capacitive touch &quot;trace&quot; which was <strong>very</strong> annoying, it took well over 15 min to align everything  correctly and solder these two traces. Then, while adding some solder, I ran my soldering iron tip in the little groove between the top and middle PCB, this had the effect of creating a &quot;solder&quot; layer between the top and middle PCB. If I had to guess, this groove is around 0.5 mm high. It wasn't planned, but it added a superb, glossy accent to the aesthetic.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003203.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003203_hu8e3216b6d3c44d01a67e479792219c4f_112622_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003311.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003311_hu5f69ae20852c9460ccfbfa0fb8cd828a_100224_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003243.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003243_huf835bb62c784b8b1a7384fd5eeb993cc_96060_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003355.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003355_hu92aa51d75017af1ec5c38180bb2e5dce_67398_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003233.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003233_hu03bb3966cba3160fa49c148c559e2993_107917_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>The last thing I did was to melt a tiny bit of hot glue on top of the LED holes, this acts a diffusing layer, making the final board look like a professional product</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003714-1.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003714-1_hu7a10894da58cc0fe8b765394c7372a08_100441_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20220410_003752-1.jpg">
                            <img src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_IMG_20220410_003752-1_huda24cd401aa3b4a39bdfd7a16022003c_91161_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>I tried my best with the photos, but it really doesn't make justice, it's so much better IRL.</p>
<h3 id="programming">
    Programming 
    
    <a class="header-link" href="#programming">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>That was a whole story in itself. I wanted to re-use the same private keys so that I didn't have to swap the u2f of every account and more importantly that I didn't have to change my gpg certificate. As a bonus point, it also means that my old keys are now my backup keys, just in case something happen.</p>
<p>First I tackled the u2f token which easy, I simply needed to dump the flash of the old card and re flash it to the new one and bam it worked like a charm. That's one card done</p>
<p>Next and final card is my GNUK token, this was different, dumping the firmware and re-flashing sort of worked. The private key was indeed present on the device but the user password and admin password didn't work, they are somehow linked to the chip itself. Not to worry, someone wrote a cool script to extract the private key provided that you have a dump of the flash and the ID of the chip, which I have:</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/rot42/gnuk-extractor" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/e1debdc532d1f41e8533e9defcd81d41_hu54741e3f38168ae889a8aac1bbfd3c68_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/e1debdc532d1f41e8533e9defcd81d41_hu54741e3f38168ae889a8aac1bbfd3c68_0_0x720_resize_q90_h2_box_3.webp' alt='Extract PGP secret keys from Gnuk / Nitrokey Start firmwares - rot42/gnuk-extractor'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/rot42/gnuk-extractor">GitHub - rot42/gnuk-extractor: Extract PGP secret keys from Gnuk / Nitrokey Start firmwares</a>
        
        
            <p>Extract PGP secret keys from Gnuk / Nitrokey Start firmwares - rot42/gnuk-extractor</p>
        
    </div>
</blockquote>
        
    

<p>
    <figure>
        <a target="_blank" href="images/dl_image-3.png" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_image-3.png" />
        </a>
        
    </figure>


I extracted the private key and was able to import it in my gpg keyring. I then proceeded to compile a fresh version of gnuk (I'll add that that was a nightmare because it was somehow missing libc, I fixed it by compiling on another machine) and flashed it to the board.</p>
<p>After this, I then transferred the 3 keys (authentication, signing, encryption) to the card itself and deleted it from my computer. After re-adopting the card, I could see that the key was indeed on the device. To finalize it I add the public infos like the cardholder name, public key URL, etc. Second card done.</p>
<p>After some testing, it seems that both card work correctly and authenticated me successfully everywhere</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>This project was a wonderful success, everything worked flawlessly, and I'm very happy with the result, I'm confident that this key is much more sturdy than the previous version</p>
<p>Doing this project really showed how easy someone could duplicate my u2f key. BUT this is NOT in my threat model. Right now, I'm not concerned that that going to steal my keys, de-solder it and dump the firmware0. If I ever need to have better security, I'll probably buy a certified key from a trusted vendor. But this is more than fine for home &amp; homelab use.</p>
<h3 id="future-plans">
    Future plans 
    
    <a class="header-link" href="#future-plans">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I already thought of some improvement like making the card smaller somehow and maybe moving to the RP2040, bringing the price down even more. But that would mean a rewrite of the whole firmware, which isn't going to happen right now.</p>
<h3 id="links">
    Links 
    
    <a class="header-link" href="#links">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>
    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/gnuk" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/7822c4903714b2d693fbfc234debf4df_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/7822c4903714b2d693fbfc234debf4df_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/gnuk development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/gnuk">GitHub - TheStaticTurtle/gnuk</a>
        
        
            <p>Contribute to TheStaticTurtle/gnuk development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/chopstx" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/a3286f77bb15ab4ebf08c3f39bee4988_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/a3286f77bb15ab4ebf08c3f39bee4988_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='Unofficial mirror of GNUK&amp;#39;s submodule: Chopstx. Contribute to TheStaticTurtle/chopstx development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/chopstx">GitHub - TheStaticTurtle/chopstx: Unofficial mirror of GNUK&amp;#39;s submodule: Chopstx</a>
        
        
            <p>Unofficial mirror of GNUK&#39;s submodule: Chopstx. Contribute to TheStaticTurtle/chopstx development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/rot42/gnuk-extractor" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/e1debdc532d1f41e8533e9defcd81d41_hu54741e3f38168ae889a8aac1bbfd3c68_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/e1debdc532d1f41e8533e9defcd81d41_hu54741e3f38168ae889a8aac1bbfd3c68_0_0x720_resize_q90_h2_box_3.webp' alt='Extract PGP secret keys from Gnuk / Nitrokey Start firmwares - rot42/gnuk-extractor'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/rot42/gnuk-extractor">GitHub - rot42/gnuk-extractor: Extract PGP secret keys from Gnuk / Nitrokey Start firmwares</a>
        
        
            <p>Extract PGP secret keys from Gnuk / Nitrokey Start firmwares - rot42/gnuk-extractor</p>
        
    </div>
</blockquote>
        
    


    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/gl-sergei/u2f-token" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/236bfe6984bb8f9deb907e46486325d5_hu145aa3aded0219d4fddc876806a8c3bf_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/236bfe6984bb8f9deb907e46486325d5_hu145aa3aded0219d4fddc876806a8c3bf_0_0x720_resize_q90_h2_box_3.webp' alt='u2f token firmware for stm32f103 and efm32hg boards - gl-sergei/u2f-token'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/gl-sergei/u2f-token">GitHub - gl-sergei/u2f-token: u2f token firmware for stm32f103 and efm32hg boards</a>
        
        
            <p>u2f token firmware for stm32f103 and efm32hg boards - gl-sergei/u2f-token</p>
        
    </div>
</blockquote>
        
    
</p>
<p>Again, thanks to <a target="_blank" href="https://www.pcbway.com/">pcbway</a> for allowing me to use their services by sponsoring these PCBs.

    <figure>
        <a target="_blank" href="images/dl_PCBway1_1-2.png" >
            <img alt="" src="/turtleauth-2-1-a-design-for-everyday-use/images/dl_PCBway1_1-2.png" />
        </a>
        
    </figure>

</p>

 ]]></content:encoded></item><item><title>Fixing a dev worst nightmare</title><description> &lt;p>Stack Overflow having some kind of maintenance is scary. In this post, I'm solving this once and for all by hosting a local copy of the answers.&lt;/p></description><link>https://blog.thestaticturtle.fr/fixing-a-dev-worst-nightmare/</link><guid>https://blog.thestaticturtle.fr/fixing-a-dev-worst-nightmare/</guid><category> Docker</category><category> Servers</category><category> Homelab</category><category> Tutorials</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 11 Feb 2022 21:25:17 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/fixing-a-dev-worst-nightmare/images/cover.png"/><media:content url="https://blog.thestaticturtle.fr/fixing-a-dev-worst-nightmare/images/cover.png" medium="image"/><content:encoded><![CDATA[ <h1>Fixing a dev worst nightmare</h1>
<span class="subtitle"><p>Stack Overflow having some kind of maintenance is scary. In this post, I'm solving this once and for all by hosting a local copy of the answers.</p></span>
<br>

    <img class="" src='/fixing-a-dev-worst-nightmare/images/cover_hu8a7b3a6c2bc622178a8b2ae99a78065a_164797_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Fixing a dev worst nightmare"/>

<hr>

<p>When writing software, there is always something that's the dev worst nightmare, be it a mix-match of tab and space in python, a missing semicolon in c, you get the point.</p>
<p>But the most scary thing is when you can't steal code search for help

    <figure>
        <a target="_blank" href="images/dl_image-11.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-11.png" />
        </a>
        
    </figure>


When it's 15min of downtime that's OK I deal with it, and I even then, I'll probably not notice, but when it's 2 hours in the middle of a project it's quite annoying. So I swore to find a solution.</p>
<p>So how do I download Stack Overflow locally on my NAS? The noobie approach would be to just scrape every question and re-host the site statically, the issue is that Stack Overflow is well... quite big and while I probably would have the space it's really stupid especially considering the second option</p>
<p>ZIM files</p>
<blockquote>
<p>ZIM stands for &quot;Zeno IMproved&quot;, as it replaces the earlier Zeno file format. Its file compression uses <a target="_blank" href="https://en.wikipedia.org/wiki/LZMA2">LZMA2</a>.</p>
<p>The <strong>ZIM</strong> file format is an open file format that stores wiki content for offline usage. Its primary focus is the contents of Wikipedia and other Wikimedia projects.</p>
<p>The format allows for the compression of articles, features a full-text search index and native category and image handling similar to MediaWiki, and the entire file is easily indexable and readable using a program like Kiwix – unlike native Wikipedia XML database dumps.</p>
</blockquote>
<h1 id="offline-it-shall-be">
    Offline it shall be! 
    
    <a class="header-link" href="#offline-it-shall-be">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Well that sounds perfect, where can I get the Stack Overflow one. Well, the kiwix wiki provides a list of content : <a target="_blank" href="https://wiki.kiwix.org/wiki/Content_in_all_languages">https://wiki.kiwix.org/wiki/Content_in_all_languages</a>.</p>
<p>Among them is Stack Overflow in multiple languages (es, ja, pt, ru) including the main one which is a mind-bending 134 GB of compressed data, that's more than the entire dump of English Wikipedia (2021-12) which is around 86 GB 😲.</p>
<p>There is another issue tho, the dump provided by kiwix dates from February 2019 which isn't that old, but, in a field such a software/firmware development which is constantly moving and innovating I feel that having a 3 years old dump might lack some newer stuff.</p>
<p>So how do we get a newer ZIM file, well searching around it turns out that the Stack Exchange post database dumps quarterly on the internet archive:</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://archive.org/download/stackexchange">https://archive.org/download/stackexchange</a>
            </div>
        </blockquote>
    

<p>Looking at the <a target="_blank" href="https://archive.org/download/stackexchange/stackoverflow.com-PostHistory.7z">stackoverflow.com-PostHistory.7z</a> we can see that the dump was done December of 2021, much better, the dump is also 162 GB 🤯</p>
<p>The thing is, I'm not in the mood of writing a script to process 162 GB of data (I will probably write it a some point because it'll probably be a great exercise about managing huge datasets).</p>
<p>So to solve this I browsed a bit more and of course reddit came to the rescue: <a target="_blank" href="https://www.reddit.com/r/DataHoarder/comments/pm8xoy/updated_stack_overflow_zim_file_20210906/">https://www.reddit.com/r/DataHoarder/comments/pm8xoy/updated_stack_overflow_zim_file_20210906/</a></p>
<p>Someone generated a zim file for September 2021 (which still works for me). This dump has 21,958,765 articles and weight 161GB, not bad!</p>
<p>Well next was downloading the dump, at least they provided a torrent, and with 3/4 peers at max it took around 2 days to download which is a lot less fun. But the important thing is that the file is sitting on my nas, Yay

    <figure>
        <a target="_blank" href="images/dl_image-12.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-12.png" />
        </a>
        
    </figure>

</p>
<h1 id="setting-up-the-reader">
    Setting up the reader 
    
    <a class="header-link" href="#setting-up-the-reader">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Ok now that I have the file I need a way to read it, the standart for reading ZIM fil is the Kiwix reader, but there are others (like <a target="_blank" href="https://github.com/birros/web-archives">web-archives</a> and multiple command line ones).</p>
<p>Kiwix it is, so I created a new container on my proxmox cluster and started things off by mount my NAS. This required me to enable the CIFS feature on my container, while I was there as I'll be using docker to launch Kiwix, I also enabled the support for nesting.

    <figure>
        <a target="_blank" href="images/dl_image-13.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-13.png" />
        </a>
        
    </figure>


I then created a file called &quot;smbcredentials&quot; at the root of the disk and wrote the logins to my NAS. For security reason, I also did a chmod 600 on the file to only allow root to open the file

    <figure>
        <a target="_blank" href="images/dl_image-14.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-14.png" />
        </a>
        
    </figure>


Then, I added two lines in /etc/fstab to mount my SMB shares to the disk, in my case I had to specify the uid/gid of the &quot;application_operator&quot; user manually because otherwise, the permissions got messed up.

    <figure>
        <a target="_blank" href="images/dl_image-15.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-15.png" />
        </a>
        
    </figure>


And would you look at that, after creating the /nas folder and executing &quot;mount -a&quot; to reload fstab, I could access my ZIM file

    <figure>
        <a target="_blank" href="images/dl_image-16.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-16.png" />
        </a>
        
    </figure>


OK now what. Well, we install docker and docker-compose and create a docker-compose.yml file to start kiwix</p>
<pre><code>version: '3'
services:
    kiwix:
        image: kiwix/kiwix-serve
        restart: unless-stopped
        command: 'stackoverflow.com_en_all.zim'
        ports:
            - '9999:80'
        volumes:
            - &quot;/nas/common/applications/wikipedia/dumps:/data&quot;
</code></pre>
<p>First, we want to use the <a target="_blank" href="https://hub.docker.com/r/kiwix/kiwix-serve">kiwix/kiwix-serve</a> image, and set it to restart unless we stop it manually.</p>
<p>The image then expects the file name as the start command, so I set it to the downloaded ZIM file (Note, multiple files can be used here by separating them with a whitespace or simply using *.zim).</p>
<p>I then forwarded the port 80 inside the container to port 9999 of the host and maped the nas folder to the /data folder inside de container.</p>
<p>After executing &quot;docker-compose up -d&quot; and waiting a bit for it to start (remember it still is a 162 GB file) I was greeted by a nice welcome page on my browser

    <figure>
        <a target="_blank" href="images/dl_image-17.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-17.png" />
        </a>
        
    </figure>


Clicking on the link loads (after some time) the article list:

    <figure>
        <a target="_blank" href="images/dl_image-18.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-18_hu23d820ecfda1af1eb4fc81c01368a90e_77707_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


And of course, clicking on a question loads the question/answer:

    <figure>
        <a target="_blank" href="images/dl_image-19.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-19.png" />
        </a>
        
    </figure>


And that's it.... for the basics</p>
<h1 id="going-even-further">
    Going even further 
    
    <a class="header-link" href="#going-even-further">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Lets take this question:

    
        

        
            









<blockquote class="embed embed-stack-overflow">
    
        
            <a target="_blank" href="https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/3d5f4f5b7f79069320b260b95774fc90_hu22b5a89ce8367627813bd9648c4031fe_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/3d5f4f5b7f79069320b260b95774fc90_hu22b5a89ce8367627813bd9648c4031fe_0_0x720_resize_q90_h2_box_3.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type">Which JSON content type do I use?</a>
        
        
            <p>There are many &amp;quot;standards&amp;quot; for the JSON content type:&#xA;application/json&#xA;application/x-javascript&#xA;text/javascript&#xA;text/x-javascript&#xA;text/x-json&#xA;&#xA;Which one do I use, and where? I assume secu...</p>
        
    </div>
</blockquote>
        
    
</p>
<p>The url <a target="_blank" href="https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type">https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type</a> can be decomposed into multiple parts:</p>
<ul>
<li>SO Domain: <a target="_blank" href="https://stackoverflow.com/questions/">https://stackoverflow.com/questions/</a></li>
<li>Question id: 477816</li>
<li>Question slug: what-is-the-correct-json-content-type</li>
</ul>
<p>It turns out that I can simply use the question ID and append it to the end of the kiwix server, like this: http://192.168.1.56:9999/stackoverflow.com_en_all/A/question/<strong>477816</strong>.html</p>
<p>And of course it loads fine:

    <figure>
        <a target="_blank" href="images/dl_image-20.png" >
            <img alt="" src="/fixing-a-dev-worst-nightmare/images/dl_image-20_hu98324eee1f00ca5a4a21bd2ab95277e3_58658_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h2 id="automated-redirection">
    Automated redirection 
    
    <a class="header-link" href="#automated-redirection">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So, lets write a bit of JS that detects when stackoverflow is in maintenance (or simply crashed) and redirect to my selfhosted Kiwix instance.</p>
<p>First I created a manifest.json file describing the extension. It also specifies that the script &quot;content.js&quot; will be executed when any page that matches the url to this glob: &quot;https://stackoverflow.com/questions/<em>/</em>&quot;</p>
<pre><code>{
    &quot;name&quot;: &quot;StackOverflow to Kiwix redirector&quot;,
    &quot;description&quot;: &quot;Redirects to a self-hosted kiwix instance if StackOverflow is down&quot;,
    &quot;version&quot;: &quot;1.0&quot;,
    &quot;manifest_version&quot;: 3,
    &quot;icons&quot;: {
        &quot;16&quot;: &quot;/images/kiwix.png&quot;,
        &quot;32&quot;: &quot;/images/kiwix.png&quot;,
        &quot;48&quot;: &quot;/images/kiwix.png&quot;,
        &quot;128&quot;: &quot;/images/kiwix.png&quot;
    },
    &quot;content_scripts&quot;: [
        {
            &quot;matches&quot;: [
                &quot;https://stackoverflow.com/questions/*/*&quot;
            ],
            &quot;js&quot;: [&quot;content.js&quot;]
        }
    ]
}
</code></pre>
<p>The script itself is pretty simple, it simply checks for the presence of the &quot;a&quot; element which contains the question title. And if it doesn't find it or errors out, redirect to the local kiwix server</p>
<pre><code>const LOCAL_URI=&quot;http://wiki.lan/stackoverflow.com_en_all/A/question/{{ID}}.html&quot;

function redirect() {
    alert(&quot;StackOverflow is broken, redirecting to local instance&quot;)
    var id = window.location.href.split(&quot;/&quot;).reverse()[1]
    window.location = LOCAL_URI.replace(&quot;{{ID}}&quot;, id)
}

window.addEventListener('load', function () {
    try {
        var title = document.querySelector(&quot;#question-header &gt; h1 &gt; a&quot;)
        if (title == null){
            redirect()
        }
    } catch (error) {
        redirect()
    }
})
</code></pre>
<h2 id="testing">
    Testing 
    
    <a class="header-link" href="#testing">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>A simple way to test if the extension is working is to open the developer mode and go into the mobile view. As the CSS of Stack Overflow is different on pc than on mobile, the query selector for the title is also different, meaning that the extension will consistently redirect to the kiwix server. See for yourself:</p>
<ul>
<li>Stack Overflow loads normally</li>
<li>Go into mobile mode</li>
<li>Stack Overflow gets redirected</li>
</ul>
<h1 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h1>
<p>Well, I just solved the biggest issue of software development, before you go hate me, this post was more to present kiwix and the ZIM file format than making sure that I always have access to Stack Overflow (it is pretty cool tho).</p>

 ]]></content:encoded></item><item><title>TrueNAS - SMB LDAP Auth</title><description> &lt;p>The very long story on how I setup LDAP authentication for my samba shares with ACLs on TrueNAS&lt;/p></description><link>https://blog.thestaticturtle.fr/truenas-smb-ldap-auth/</link><guid>https://blog.thestaticturtle.fr/truenas-smb-ldap-auth/</guid><category> Servers</category><category> Homelab</category><dc:creator> Samuel</dc:creator><pubDate>Wed, 08 Dec 2021 23:55:17 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/truenas-smb-ldap-auth/images/cover_hu4adad23deabcada176af21de79f5d8be_64816_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/truenas-smb-ldap-auth/images/cover_hu4adad23deabcada176af21de79f5d8be_64816_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>TrueNAS - SMB LDAP Auth</h1>
<span class="subtitle"><p>The very long story on how I setup LDAP authentication for my samba shares with ACLs on TrueNAS</p></span>
<br>

    <img class="" src='/truenas-smb-ldap-auth/images/cover_hu4adad23deabcada176af21de79f5d8be_64816_1350x900_fit_q80_bgffffff_box_3.jpg' alt="TrueNAS - SMB LDAP Auth"/>

<hr>

<p><strong>Disclaimer: This is not a tutorial, just a lot of notes about how I setup SMB shares with LDAP auth on TrueNAS</strong></p>
<p>So with my new NAS I thought it might be nice to do things properly and have a centralized user system for all the other apps that I have.</p>
<p>First thing that comes to mind is an AD there are a few issues with that:</p>
<ul>
<li>Windows server consumes a lot of ram, cost money and is quite frankly overkill for my home use right now. To solve this you have opensource and/or free stuff like zentyal or univention UCS (Which after testing work pretty well)</li>
<li>Next to use it I need to either enter the domain name before the user (eg MYDOMAIN\user) or actually join the computer to the domain which is a big no no no for my current network (I would do it if I was setting everything for the first time)</li>
</ul>
<p>Next I found FreeIPA which is pretty cool, and I probably could get it to work now but when I started last week I could not get it to work with the smb shares. This is one thing I might go back to and try again</p>
<h2 id="installing-openldap">
    Installing OpenLDAP 
    
    <a class="header-link" href="#installing-openldap">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So next I went to town and installed OpenLDAP this was the easy part this tutorial pretty much explains it all <a target="_blank" href="https://computingforgeeks.com/install-and-configure-openldap-server-ubuntu/">https://computingforgeeks.com/install-and-configure-openldap-server-ubuntu/</a> . The main things are:</p>
<ul>
<li>Have a name for the ldap server and put it in the host files (I'm using fluffy.lan.xxxxxx.fr with 192.168.1.52)</li>
<li>Install slapd and ldap-utils</li>
<li>Add the base OUs (groups, people and machines)</li>
<li>Don't create a user yet</li>
</ul>
<h3 id="yay-lets-add-ssltls">
    Yay, let's add SSL/TLS 
    
    <a class="header-link" href="#yay-lets-add-ssltls">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>So I don't know why it's need but somehow doesn't work without.</p>
<p>As I'm trying to set up a proper network, I didn't want to just generate certificates on the LDAP container and transfer them to machines on the network afterwards.</p>
<p>So I launched XCA and created a new DB, created a CA certificate/pkey combo and an TLS certificate/pkey for the LDAP server

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image.png" />
        </a>
        
    </figure>


I then put the CA certificate and LDAP TLS certificate under /etc/ssl/certs/ and the LDAP TLS private key under /etc/ssl/private/</p>
<p>Then followed this tutorial <a target="_blank" href="https://computingforgeeks.com/secure-ldap-server-with-ssl-tls-on-ubuntu/">https://computingforgeeks.com/secure-ldap-server-with-ssl-tls-on-ubuntu/</a> skipping the part about generating the certificates (start at step 2) and using the proper paths.</p>
<p>I also added the certificates in **every **config file possible, added the ldaps:/// service in /etc/default/slapd</p>
<p>And it worked, yay</p>
<h2 id="adding-ldap-to-truenas">
    Adding LDAP to TrueNAS 
    
    <a class="header-link" href="#adding-ldap-to-truenas">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Adding LDAP auth was really easy, just pop in the hostname, base and bind DNs (+ the password) and that was it

    <figure>
        <a target="_blank" href="images/dl_image-1.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-1.png" />
        </a>
        
    </figure>


At this point, I had a test account on my LDAP server and was able to ssh to TrueNAS with this user</p>
<h2 id="adding-ldap-to-samba">
    Adding LDAP to samba 
    
    <a class="header-link" href="#adding-ldap-to-samba">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>That's the tricky part, as there is so little documentation / forum post that you might as well say that there is none</p>
<p>After many hours of searching, I found this note in the truenas documentation:

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-2.png" />
        </a>
        
    </figure>

</p>
<h3 id="adding-the-samba-attributes">
    Adding the samba attributes 
    
    <a class="header-link" href="#adding-the-samba-attributes">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Again, doing the <a target="_blank" href="https://wiki.samba.org/index.php/4.1_smbldap-tools">smbldap-tools</a> is the tricky part. The Ubuntu tutorial is pretty good (<a target="_blank" href="https://guide.ubuntu-fr.org/server/samba-ldap.html">https://guide.ubuntu-fr.org/server/samba-ldap.html</a>) a few things were different tho:</p>
<ul>
<li>The samba.ldif example file was not gzipped, and I could just pipe it to ldapadd</li>
<li>You really can't mess up while doing smbldap-config:</li>
</ul>
<p>
    <figure>
        <a target="_blank" href="images/dl_image-3.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-3.png" />
        </a>
        
    </figure>

</p>
<ul>
<li>LDAP Suffix is the base DN</li>
<li>LDAP group/user/machine suffixes are the corresponding OUs when installing OpenLDAP.</li>
<li>Master/Slave Bind DN is well the bind DN (I used the admin account)</li>
<li>The default SID for the WORKGROUP domain will f*** you up later, but it's easy to change</li>
</ul>
<p>The command smbldap-populate should work without any issues:

    <figure>
        <a target="_blank" href="images/dl_image-5.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-5.png" />
        </a>
        
    </figure>


Then using an LDAP browser you should see the root and nobody users in the people OUs:

    <figure>
        <a target="_blank" href="images/dl_image-11.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-11.png" />
        </a>
        
    </figure>


The most important part is the samba attributes, if they aren't there something is messed up</p>
<h3 id="creating-a-user">
    Creating a user 
    
    <a class="header-link" href="#creating-a-user">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>It's actually really easy, you just' can't do it remotely in order to populate the samba fields:</p>
<p><code>smbldap-useradd -a -d /mnt/main/home/my_user -N my_user -P -B 0 -s /bin/bash my_user </code></p>
<h3 id="logging-in">
    Logging in 
    
    <a class="header-link" href="#logging-in">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Actually, using smbclient with a user in the LDAP does not work

    <figure>
        <a target="_blank" href="images/dl_chrome_2021-12-07_22-14-36.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_chrome_2021-12-07_22-14-36.png" />
        </a>
        
    </figure>


Turns out it didn't even find the user

    <figure>
        <a target="_blank" href="images/dl_putty_2021-12-07_22-32-03.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_putty_2021-12-07_22-32-03.png" />
        </a>
        
    </figure>


I thought that TrueNAS would automatically configure samba to use ldap what a naive mistake why the hell would something work when you activate it...</p>
<p>So after search for I while I figured out that you can either have local login or ldap login fine I guess but not ideal if the LDAP server fails.</p>
<p>To configure samba, I wrote this in the auxiliary parameters of the SMB service settings:</p>
<pre><code>passdb backend = ldapsam:ldap://192.168.1.52

ldap admin dn = cn=admin,dc=lan,dc=xxxxxx,dc=fr
ldap group suffix = ou=groups
ldap machine suffix = ou=machines
ldap passwd sync = yes
ldap suffix = dc=lan,dc=xxxxxx,dc=fr
ldap ssl = no
ldap user suffix = ou=people

idmap config * : range = 10000-39999
ldapsam:trusted = yes
idmap config * : backend = tdb
</code></pre>
<p>This translates to this full config

    
        

        
            









<blockquote class="embed embed-gist">
    
        
            <a target="_blank" href="https://gist.github.com/TheStaticTurtle/6536fa5ab0bea2286c5cbd882df78dc2" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/e1243c1243ed74cc7f9615599f65ad74_hua2668f9121241e579d444a358e180d86_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/e1243c1243ed74cc7f9615599f65ad74_hua2668f9121241e579d444a358e180d86_0_0x720_resize_q90_h2_box_3.webp' alt='TrueNAS Samba ldap config . GitHub Gist: instantly share code, notes, and snippets.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://gist.github.com/TheStaticTurtle/6536fa5ab0bea2286c5cbd882df78dc2">TrueNAS Samba ldap config </a>
        
        
            <p>TrueNAS Samba ldap config . GitHub Gist: instantly share code, notes, and snippets.</p>
        
    </div>
</blockquote>
        
    
</p>
<p>That was not the end tho because now using smbclient printed out this error:

    <figure>
        <a target="_blank" href="images/dl_chrome_2021-12-07_22-55-34.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_chrome_2021-12-07_22-55-34.png" />
        </a>
        
    </figure>


After starting smbd manually once again, this was the actual error:</p>
<p><code>The primary group domain sid(S-1-5-21-1836219694-1107345289-1677364427-513) does not match the domain sid(S-1-5-21-3359593988-3909439102-1203953793) for david(S-1-5-21-3359593988-3909439102-1203953793-10004)</code></p>
<p>When I think about it, the domain SID is clearly wrong, and it's using the SID from the FLUFFY domain instead of the actual one, so I got the sid for it and modified every ldap entry (groups included) by replacing <code>S-1-5-21-3359593988-3909439102-1203953793</code> (fluffy) to <code>S-1-5-21-1836219694-1107345289-1677364427</code> (which is the value that worked for me).</p>
<p>Then as a good measure I also modified the SID= in the /etc/smbldap-tools/smbldap.conf file</p>
<p>Next came a lof of joy:

    <figure>
        <a target="_blank" href="images/dl_image-8.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-8.png" />
        </a>
        
    </figure>


It actually worked and sure enough it mounted just fine

    <figure>
        <a target="_blank" href="images/dl_image-9.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-9.png" />
        </a>
        
    </figure>

</p>
<h3 id="groups-and-acls">
    Groups and ACLs 
    
    <a class="header-link" href="#groups-and-acls">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>So right now I have two shares, the home one and the media one (containing photos and videos, probably audio at some point)</p>
<p>To start, I created a few groups with smbldap-groupadd and reorganized them.

    <figure>
        <a target="_blank" href="images/dl_image-10.png" >
            <img alt="" src="/truenas-smb-ldap-auth/images/dl_image-10.png" />
        </a>
        
    </figure>


Then I edited all the ACLs on TrueNAS to look like this:</p>
<p>/mnt/main/home</p>
<pre><code>User: root
Group: Gringotts User

ACLs:
@group ALLOW Traverse
@owner ALLOW Full
</code></pre>
<p>/mnt/main/media</p>
<pre><code>User: root
Group: Gringotts User

ACLs:
@group ALLOW Read
@owner ALLOW Full
</code></pre>
<p>/mnt/main/media/photo</p>
<pre><code>User: root
Group: Gringotts User

ACLs:
@owner ALLOW Full
&quot;Gringotts Photo Read&quot; ALLOW Read
&quot;Gringotts Photo Write&quot; ALLOW Full
</code></pre>
<p>/mnt/main/media/video</p>
<pre><code>User: root
Group: Gringotts User

ACLs:
@owner ALLOW Full
&quot;Gringotts Video Read&quot; ALLOW Read
&quot;Gringotts Video Write&quot; ALLOW Full
</code></pre>
<p>These ACLs only allow access to members of the &quot;Gringotts User&quot; group to the shares and granularly the Read/Write permissions for the video and photo dataset</p>
<p>**Note: **I did have to restart smbd on TrueNAS manually to update the permissions</p>
<h3 id="heading">
     
    
    <a class="header-link" href="#heading">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Conclusion</p>
<p>I'm very happy on how it turned out, the LDAP is pretty easy to manage and sync-up pretty fast. There are a few things that could be better / will do next:</p>
<ul>
<li>If I had to set up a network from scratch, I would have gone with an AD and joined every computer because it's much easier</li>
<li>Setting up LDAP for samba sucks and is really annoying to do</li>
<li>I need to code an interface for managing users easily because LdapAccountManager only somewhat works</li>
<li>I'm going to make 3 backups of this container because it was so annoying to do</li>
</ul>
<h3 id="resources">
    Resources 
    
    <a class="header-link" href="#resources">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Main resources / websites used for this setup:</p>
<ul>
<li><a target="_blank" href="https://computingforgeeks.com/install-and-configure-openldap-server-ubuntu/">https://computingforgeeks.com/install-and-configure-openldap-server-ubuntu/</a></li>
<li><a target="_blank" href="https://www.ixsystems.com/documentation/truenas/11.1/directoryservice.html#ldap">https://www.ixsystems.com/documentation/truenas/11.1/directoryservice.html#ldap</a></li>
<li><a target="_blank" href="https://guide.ubuntu-fr.org/server/samba-ldap.html">https://guide.ubuntu-fr.org/server/samba-ldap.html</a></li>
<li>Forum posts on truenas.com (couldn't find them)</li>
</ul>

 ]]></content:encoded></item><item><title>Wireless audio part 2 - Working prototype</title><description> &lt;p>Second version of my CC85XX wireless audio transceiver prototype&lt;/p></description><link>https://blog.thestaticturtle.fr/wireless-audio-part-2-working-prototype/</link><guid>https://blog.thestaticturtle.fr/wireless-audio-part-2-working-prototype/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Mon, 13 Sep 2021 18:15:33 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/wireless-audio-part-2-working-prototype/images/cover_hueffb49022fcdba60eae29731796cd208_369949_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/wireless-audio-part-2-working-prototype/images/cover_hueffb49022fcdba60eae29731796cd208_369949_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Wireless audio part 2 - Working prototype</h1>
<span class="subtitle"><p>Second version of my CC85XX wireless audio transceiver prototype</p></span>
<br>

    <img class="" src='/wireless-audio-part-2-working-prototype/images/cover_hueffb49022fcdba60eae29731796cd208_369949_1350x900_fit_q80_box.jpg' alt="Wireless audio part 2 - Working prototype"/>

<hr>

<p>A while ago I design the first prototype of my wireless audio system based on the CC85XX chip family from Text Instruments</p>

    
        

        
            








    


<blockquote class="embed embed-blog_thestaticturtle_fr">
    
        
            <a target="_blank" href="https://blog.thestaticturtle.fr/wiau-part1-1stprototype/" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/04117d49352a659b80549915057b6167_hu8acef0f4899aeaec1cab56f0b6f4e2d1_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/04117d49352a659b80549915057b6167_hu8acef0f4899aeaec1cab56f0b6f4e2d1_0_0x720_resize_q90_h2_box.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://blog.thestaticturtle.fr/wiau-part1-1stprototype/">TheStaticTurtle - Wireless audio part 1 - 1st prototype</a>
        
        
            <p>Making the first prototype of my wireless audio system using Ti's CC85XX series chips</p>
        
    </div>
</blockquote>
        
    

<p>The first prototype had a lot of issues but the most annoying one being that I couldn't go further than 1 meter I also had issues with the TLV320AIC3204 only working on the breakout board.</p>
<p>In the post I said that I was redesigning a schematic and a PCB, the truth is that I actually had one ready before I published the post. I spent way too much time re-reading datasheets of all core part: the CC8531, the CC5920 and the TLV320AIC3204.

    <figure>
        <a target="_blank" href="images/dl_Schematic_WiAu_V1.1.0_2021-09-12.png" >
            <img alt="" src="/wireless-audio-part-2-working-prototype/images/dl_Schematic_WiAu_V1.1.0_2021-09-12_hu069810ead535b9d8ae1b7ba77e5525e8_127850_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


The only reason I didn't manufacture the board immediately is just in case I thought of a mistake later, and kinda forgot about it. Last month I placed an order for other PCBs and I add this one.</p>
<p>This PCB is pretty much the bare-bones, I removed the BMS and the mic preamp, I also paid a lot more attention to the capacitor placements and added resistors from the example schematics.</p>
<p>And out came this PCB:

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/wireless-audio-part-2-working-prototype/images/dl_image_hu245d961d554512c3437cf70e861c6f9b_91084_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


After spending around 5h to assemble two of them, here is the result:</p>
<p>I've tested it for +24h and if I stay &quot;close&quot; (1/2 meters) to the transmitter it works fine without any dropouts, any more and the signal is weaker starts to drop out sometimes. Other issues of this version include: messed-up audio jack pins, no mic preamp, no BMS and minor issues with the rf part.</p>
<p>Next thing is to study a bit more on rf design and the cc2590, get the freaking jacks pinout right, and find better buttons for a potential case.</p>
<p>Hopefully the V3 will work first try without any major issues and a decent range.</p>

 ]]></content:encoded></item><item><title>Using docker to contain projects</title><description> &lt;p>How I converted my own NodeJS s3 bucket caching system to a simple docker container for easy deployment of servers / scripts&lt;/p></description><link>https://blog.thestaticturtle.fr/using-docker-to-contain-projects/</link><guid>https://blog.thestaticturtle.fr/using-docker-to-contain-projects/</guid><category> Web</category><category> Docker</category><category> Node js</category><category> Servers</category><category> Tutorials</category><dc:creator> Samuel</dc:creator><pubDate>Sat, 03 Apr 2021 15:44:30 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/using-docker-to-contain-projects/images/cover_hu79d1369a6d9ea9a16fdcc0d8c3f40efa_19743_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/using-docker-to-contain-projects/images/cover_hu79d1369a6d9ea9a16fdcc0d8c3f40efa_19743_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Using docker to contain projects</h1>
<span class="subtitle"><p>How I converted my own NodeJS s3 bucket caching system to a simple docker container for easy deployment of servers / scripts</p></span>
<br>

    <img class="" src='/using-docker-to-contain-projects/images/cover_hu79d1369a6d9ea9a16fdcc0d8c3f40efa_19743_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Using docker to contain projects"/>

<hr>

<p>About a year ago I moved all of my image storage to an S3-compatible api provided by OVH. What appealed me the most was the pricing €0.01 / per GB / per month there was however an added cost of €0.01 / per GB download which I didn't like, so I build a little tool that allowed me to cache the images / files stored on the bucket</p>
<p>The tool in question is available on my GitHub:

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/NodeCached" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/7cd90ee97971e3e9551de99f54e52231_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/7cd90ee97971e3e9551de99f54e52231_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/NodeCached development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/NodeCached">GitHub - TheStaticTurtle/NodeCached</a>
        
        
            <p>Contribute to TheStaticTurtle/NodeCached development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    
</p>
<p>The idea is to take the path of the image hash it download the image and rename it to this hash. Next every time we want to access said image the script will serve the cached version. I've also implemented a limit so that it doesn't fill my disk which would defeat the purpose.</p>
<p>The script worked great for a while and then there was the OVH incident where a datacenter caught fire. After the server was restarted I modified the script so that the old cached files could still be used (as the s3 api of my datacenter wasn't restarted yet).</p>
<p>But after 2-3 days I thought that I should have a plan in case something like this ever happened again. I wanted to have the main website (and core component lie the caching system) in one big docker-compose.yml file</p>
<p>So obviously next to come was to build a container of my caching system. The little script is made with NodeJS and first need some remodeling to accept configs in the form of environment variable.</p>
<p>Next I could create a file name Dockerfile at the root of the project with this content:</p>
<pre><code>FROM node:14

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 2486
CMD [ &quot;node&quot;, &quot;index.js&quot; ]
</code></pre>
<p>Every Dockerfile starts with a FROM line stating from which container it should start with in my case since I built the project in NodeJS I choose the container node with the version 14</p>
<p>Next you need to say where your app will be located inside the container in my case I used /user/src/app</p>
<p>The next part is specific to NodeJS, I copied the package.json over in the container and run &quot;npm install&quot; to install my script dependencies (It would be approximately the same with pip and the requirements.txt file for a python project)</p>
<p>After that I copied the whole project in the directory.</p>
<p>I then stated that my app uses the port 2486 and that it should be exposed (to other containers or redirected to the host later)</p>
<p>Next I said that to run the container docker should do &quot;node index.js&quot;</p>
<p>Then in the directory of the Dockerfile I executed</p>
<p><code>docker build -t thestaticturtle/nodecached .</code></p>
<p>to build the container, you could next use &quot;docker push&quot; to send it to the docker hub</p>
<p>After all of this you can use your freshly build container in a docker-compose file for example:</p>
<pre><code>version: &quot;3&quot;
services:
  object_proxy:
    image: thestaticturtle/nodecached:latest
    restart: always
    networks:
      - backend
    volumes:
      - ./data/object-proxy:/usr/src/app/cache/
    ports:
      - 2486:2486
    environment:
      cds__base_url: &quot;url_of_your_s3_server&quot;

networks:
  backend:
    driver: bridge
</code></pre>
<p>Note that I also specified a volume, that is so that I can access the cached images of my app with going into the container itself</p>
<p>If you want to try it my container is available at: <a target="_blank" href="https://hub.docker.com/r/thestaticturtle/nodecached">https://hub.docker.com/r/thestaticturtle/nodecached</a></p>

 ]]></content:encoded></item><item><title>Bridging the gap between old and new, garage door gets a Wi-Fi controller</title><description> &lt;p>Building a stable and secure garage door controller that interfaces to the original controller&lt;/p></description><link>https://blog.thestaticturtle.fr/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/</link><guid>https://blog.thestaticturtle.fr/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/</guid><category> 3 d</category><category> Diy</category><category> Electronics</category><category> Iot</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 25 Feb 2021 13:26:36 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/cover_hu54a28f823ee8df9b843bf287790a876e_470783_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/cover_hu54a28f823ee8df9b843bf287790a876e_470783_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Bridging the gap between old and new, garage door gets a Wi-Fi controller</h1>
<span class="subtitle"><p>Building a stable and secure garage door controller that interfaces to the original controller</p></span>
<br>

    <img class="" src='/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/cover_hu54a28f823ee8df9b843bf287790a876e_470783_1350x900_fit_q80_box.jpg' alt="Bridging the gap between old and new, garage door gets a Wi-Fi controller"/>

<hr>

<p>This project started a long time ago, the idea is to be able to control the two garage doors remotly.</p>
<p>The garage door that we have is a BFT TIR 60-120 fortunatly the manual (<a target="_blank" href="https://www.bft.cz/privat/navody/poh_sekcni/en/TIR%2060-120.pdf">https://www.bft.cz/privat/navody/poh_sekcni/en/TIR 60-120.pdf</a>) includes a cut-down schematic of controller board

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-2.png" />
        </a>
        
    </figure>


Before anything else, you have to understand how the door is working, each door has one button, press it, it opens, press it again (in the middle) the door stops, press it again the door closes.</p>
<p>One problem common to all version is that the original button and the rf remote need to be kept functional &quot;just in case&quot;</p>
<h3 id="1st-version">
    1st version 
    
    <a class="header-link" href="#1st-version">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The 1st version that I made was a simple Arduino with an ENC28J60 and a relay board. You could open the door by sending a UDP packet to the Arduino, and it would power the corresponding relay bridging the door button, after a while I added an authentication message that needed to be sent before sending a command.</p>
<h3 id="2nd-version">
    2nd version 
    
    <a class="header-link" href="#2nd-version">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The 1st version had a major flaw, it's freaking hard to send a UDP packet from an iPhone with a nice interface without a developer license and all. So the second version used a web interface works great except that it broke every 24h so I kind of dropped it.</p>
<h3 id="3rd-version">
    3rd version 
    
    <a class="header-link" href="#3rd-version">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The 3rd version has been designed and built to be much more stable/secure.</p>
<p>So, I had multiple options, using a state machine that updates by listening at the button isn't a very good solution because a desynchronization could happen and f-up the whole controller. Instead, I wanted feedback on where the door is (or will be) so it was time to reverse engineer the original controller board.</p>
<p>After a bit of thinking using the STOP-CLS and STOP-OPN switches NC signals (which were unpopulated) are perfect to find out if the door is open or closed. However, if the door is stopped in the middle and if we send an order to either open or close the door I also need to know on which direction to door is going in order to stop it and repress the button to let the door go in the other direction.</p>
<p>In order to do that, my best option was to use two DC optocouplers on the motor terminals in order to determinate the direction of rotation of the motor.</p>
<p>At first, I wanted to avoid soldering myself to the board and only use the available terminal blocks. However, an issue with that is that there was no DC positive to power the optocouplers that was available.</p>
<p>So after much time taking photos of the main board:</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_184855-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_184855-1_huf465919f5a44d21cdeaaa9b5ec2e921f_267967_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210212_214143__01-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210212_214143__01-1_hu521e44da852b74e2dcf9310e57951d08_255648_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_175909-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_175909-1_hufd8e87744da797ca935877fdc7fe72df_253069_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_175930-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_175930-1_hu114e8bf5ed006017d478be405c4cff16_291902_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_180124-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_180124-1_hu77e26c965e09ad85aa08fc201a461c48_235612_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>After many hours I produced a schematic with all the power parts of the circuit:

    <figure>
        <a target="_blank" href="images/dl_image-4.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-4_hub2c073d8d7b1dc9d5733099806a15b0f_240530_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


The schematic might not be exactly right, but it's good enough to get a grasp of essentials.</p>
<p>I then decided that the only thing to do is to solder myself to the bridge rectifier that ways I get a ground and a positive.</p>
<h2 id="add-on-board-schematic">
    Add-on board schematic 
    
    <a class="header-link" href="#add-on-board-schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The next step was to make a schematic for the board that will interface with the existing control board. One challenge that I add to myself was that I didn't want to buy more components for this project</p>
<p>PS: At the time of making the schematic, I wasn't sure what component I'll use, so there are some unnecessary part in order to add more compatibility</p>
<h3 id="powering-the-board">
    Powering the board 
    
    <a class="header-link" href="#powering-the-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Powering the board will be done via the 20V AC out of the controller board

    <figure>
        <a target="_blank" href="images/dl_image-5.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-5.png" />
        </a>
        
    </figure>

</p>
<h3 id="rotation-direction-of-the-motor">
    Rotation direction of the motor 
    
    <a class="header-link" href="#rotation-direction-of-the-motor">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>As I said, the motor rotation direction is used via two optocouplers:

    <figure>
        <a target="_blank" href="images/dl_image-6.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-6.png" />
        </a>
        
    </figure>

</p>
<h3 id="door-openclosed-buttons">
    Door open/closed buttons 
    
    <a class="header-link" href="#door-openclosed-buttons">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>This one is a bit tricker, I could have done some tricks and used a level shifter, but I went on the simpler route and used two more optocouplers.</p>
<p>As I said I soldered a wire on the output terminals of the bridge rectifier and used a zener diode to make the output a known voltage (The motor which is powered by the rectifier has two speeds, see the board schematic)

    <figure>
        <a target="_blank" href="images/dl_image-7.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-7.png" />
        </a>
        
    </figure>

</p>
<h3 id="door-button">
    Door button 
    
    <a class="header-link" href="#door-button">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The last connection is the door button for which I used a relay

    <figure>
        <a target="_blank" href="images/dl_image-8.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-8.png" />
        </a>
        
    </figure>


(I forgot the fly back diode, but I'll add it later on the back of the PCB)</p>
<h3 id="mcu">
    MCU 
    
    <a class="header-link" href="#mcu">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>For the mcu I choose a simple esp8266 the only issue is that I didn't know if I wanted to use a d1 mini of directly solder an esp12 to the board. So I added circuitry for both on the schematic:

    <figure>
        <a target="_blank" href="images/dl_image-9.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-9.png" />
        </a>
        
    </figure>


After a quick breadboard prototyping to validate the schematic

    <figure>
        <a target="_blank" href="images/dl_image-10.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-10_hu1b6303c099eb8d4cf386beae774229df_5231705_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


I started to design the board pcb</p>
<h2 id="add-on-board-pcb">
    Add-on board PCB 
    
    <a class="header-link" href="#add-on-board-pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The design was quite simple. You will see on the bottom left of the pcb there are connectors, one is for an FTDI, one is the I2C bus and the last one is two spare gpio (gpio2 and the adc) these ports are for future expansion or some sensors</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_chrome_2021-02-18_18-35-52.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_chrome_2021-02-18_18-35-52_hu05c036bc885f290794149967bd8f1fb4_201434_0x720_resize_q90_h2_box_3.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_chrome_2021-02-18_18-36-16.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_chrome_2021-02-18_18-36-16_huf27030cd2f86961e1ff2ede7fe449dea_105919_0x720_resize_q90_h2_box_3.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_chrome_2021-02-18_18-37-12-2.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_chrome_2021-02-18_18-37-12-2_hu61c4cce6d6261c28118b08dc004b84ed_268251_0x720_resize_q90_h2_box_3.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>Thankfully, PCBWay stepped in and offered to manufacture the PCB

    <figure>
        <a target="_blank" href="images/dl_PCBway1_1-1.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_PCBway1_1-1.png" />
        </a>
        
    </figure>

</p>
<h2 id="soldering-the-add-on-board">
    Soldering the add-on board 
    
    <a class="header-link" href="#soldering-the-add-on-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After waiting one week for the pcb to be made</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210224_184719.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210224_184719_hud26929d08dfd75b7c3b945b20057613f_354545_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210224_184705-1.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210224_184705-1_hudc71ab4c2ae5fa72e2b3133039ae021b_560869_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>After a bit of inspection, I couldn't find anything wrong with all the 5 boards and the pcb quality is quite good, on one hand the red might not be as vibrant as jlcpcb (but that's looking for something it's not really important) on the other hand the silk screen and the copper traces are fantastic.</p>
<p>Overall I'm very pleased with <a target="_blank" href="https://www.pcbway.com/">pcbway </a>PCBs and after a quick soldering job, I got a wonderful looking board:

    <figure>
        <a target="_blank" href="images/dl_IMG_20210224_162414.jpg" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210224_162414_hu54a28f823ee8df9b843bf287790a876e_470783_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


Best thing is that my smd footprints with through hole worked like a charm, even the esp12 to wemos d1 mini footprint worked. The first board uses a d1 mini directly the second board an esp12 (with the proper passives and a 3.3v regulator)</p>
<p>After a quick test, I needed to swap the relay contact and swap a pin in software. After that, the board worked just fine and responded to all my commands

    <figure>
        <a target="_blank" href="images/dl_IMG_20210224_102006.jpg" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210224_102006_hu0a6e2c881044e2e9bf8d635fad58e396_588892_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="software-for-the-add-on-board">
    Software for the add-on board 
    
    <a class="header-link" href="#software-for-the-add-on-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>All the software development has been done under the Arduino IDE (to simplfy some stuff) instead of using platformIO</p>
<p>The esp8266 connects to my home Wi-Fi network and then connect to the MQTT server running on my home automation PC.</p>
<p>The node reports the door status and it's status over the serial port, mqtt and to my syslog server.</p>
<p>All the functions use two structs, one for reading the state of the door and an other one for sending commands:</p>
<pre><code>enum door_status_t {
  DS_OPEN           = 0,
  DS_OPEN_PARTIAL   = 1,
  DS_OPENING        = 2,
  DS_CLOSING        = 3,
  DS_CLOSED         = 4,
  DS_ERROR_OPENING_AND_CLOSING   = 248,
  DS_ERROR_CLOSING_WHILE_OPENING = 249,
  DS_ERROR_OPEN_AND_CLOSED       = 250,
  DS_ERROR_OPENING_WHILE_CLOSED  = 251,
  DS_ERROR_UNKNOWN               = 252,
  DS_ERROR_ALL_ON                = 253,
  DS_UNKNOWN = 255,
};
enum move_direction_t {
  MV_STOP   = 0,
  MV_OPEN   = 1,
  MV_CLOSE  = 2,
  //Specials use case
  MV_ANY    = 3,
  MV_CHANGE = 4,
};
</code></pre>
<p>The main function responsible to move the door is this one:</p>
<pre><code>bool move_door(move_direction_t direction, int _recur) {
  door_status_t status = getDoorStatus();
  if(direction == MV_OPEN  &amp;&amp; (status == DS_OPEN || status == DS_OPENING)) return true;
  if(direction == MV_CLOSE &amp;&amp; (status == DS_CLOSED || status == DS_CLOSING)) return true;
  if(direction == MV_STOP  &amp;&amp; !(status == DS_OPENING || status == DS_CLOSING)) return true;
  
  if(_recur &gt;= 5) {
    syslog.log(LOG_WARNING, &quot;move_door maximum recusivity was hit&quot;);
    send_pin_status_all(); send_status_update_all(getDoorStatus());
    return false;
  }
  
  press_action();
  
  if(wait_for_motor_dir(MV_CHANGE, 2000)) {
    syslog.log(LOG_ERR, &quot;wait_for_motor_dir MV_ANY timeout.&quot;);
    send_pin_status_all(); send_status_update_all(getDoorStatus());
    return false;
  }
  delay(50);
  send_status_update_all(getDoorStatus());
  return move_door(direction, _recur+1);
}
</code></pre>
<p>First we get the status of the door, then we check if it's the state we want the door to be in, if it's the case, exit the function right away.</p>
<p>Otherwise, press the button (activate the relay), wait for the motor to change the state it is right now send a status update and re-execute the function, this goes on until either the door is going to be in the position we want her to be or either it is on the position we want her to be (or the maximum resistivity security limit was hit).</p>
<p>It is possible tho that the door won't respond to the relay, if it is the case stop the function and report to syslog the error.</p>
<p>The wait function is this one:</p>
<pre><code>bool wait_for_motor_dir(move_direction_t direction, int timeout) {
  unsigned long start_time = millis();
  bool isOpening = !digitalRead(pin_motor_opening);
  bool isClosing = !digitalRead(pin_motor_closing);
  bool isOpening_original = isOpening;
  bool isClosing_original = isClosing;
  
  while((start_time+timeout) &gt; millis()) {
    isOpening = !digitalRead(pin_motor_opening);
    isClosing = !digitalRead(pin_motor_closing);
    if(direction==MV_OPEN  &amp;&amp; isOpening) return true;
    if(direction==MV_CLOSE &amp;&amp; isClosing) return true;
    if(direction==MV_ANY   &amp;&amp; (isClosing || isOpening)) return true;
    if(direction==MV_STOP  &amp;&amp; !isOpening &amp;&amp; !isClosing) return true;
    if(direction==MV_CHANGE  &amp;&amp; (isOpening != isOpening_original || isClosing != isClosing_original)) return true;
    ESP.wdtFeed();
  }

  delay(75);
  return false;
}
</code></pre>
<p>First, we read the status of the motor (for the MV_CHANGE), next, we enter a loop where we get the status of the motor, and check if it is the status we want if yes return true. If the timeout is hit just return false.</p>
<p>This function shouldn't run long to cause any problem with the OTA library or anything else. However, should an error occur somewhere the ESP.wdtFeed(); is there to prevent the ESP from rebooting and not sending an error message</p>
<h3 id="home-assistant-integration">
    Home assistant integration 
    
    <a class="header-link" href="#home-assistant-integration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Here I ran into a bit of a problem, you see here are the message transmitted via mqtt:

    <figure>
        <a target="_blank" href="images/dl_MQTT_Explorer_2021-02-18_19-26-42.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_MQTT_Explorer_2021-02-18_19-26-42.png" />
        </a>
        
    </figure>


You'll see that I transmit two door_status messages. The issue is that homeassitant doesn't support a mqtt cover with a status &quot;partially_opened&quot; so I tried the position topic instead of the status one, but then I didn't have the opening/closing message on ha. So I just gave up and treated partially_opened as open hence the door_status_4state topic.</p>
<p>Here is the homeassitant configuration:</p>
<pre><code>cover:
  - platform: mqtt
    device_class: garage
    name: &quot;Porte garage david&quot;
    availability_topic: &quot;iot/garrage/door/1/available&quot;
    payload_available: &quot;yes&quot;
    payload_not_available: &quot;no&quot;
    command_topic: &quot;iot/garrage/door/1/command&quot;
    payload_open: &quot;open&quot;
    payload_close: &quot;close&quot;
    payload_stop: &quot;stop&quot;
    state_topic: &quot;iot/garrage/door/1/door_status_4state&quot;
    state_closed: &quot;closed&quot;
    state_open: &quot;open&quot;
    state_closing: &quot;closing&quot;
    state_opening: &quot;opening&quot;
</code></pre>
<p>The result is a nice entity showing up on the dashboard:

    <figure>
        <a target="_blank" href="images/dl_image-13.png" >
            <img alt="" src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_image-13.png" />
        </a>
        
    </figure>

</p>
<h3 id="case-for-add-on-board">
    Case for add-on board 
    
    <a class="header-link" href="#case-for-add-on-board">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>Of course, I can't leave a PCB hanging around on the ceiling, so I modeled a little case that I could stick on the side of the garage door original controller.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2021-02-24_19-22-15.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_SLDWORKS_2021-02-24_19-22-15_hu5b8c327d05e80dd2ae4a1d9bd81f28b8_57899_0x720_resize_q90_h2_box_3.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2021-02-24_19-22-37.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_SLDWORKS_2021-02-24_19-22-37.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2021-02-24_19-24-45.png">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_SLDWORKS_2021-02-24_19-24-45.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>After some &quot;quick&quot; 3d printing I assembled the case and fixed it to the garage door original box.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_121611.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_121611_hu2f1ee3af31a78b41deba6ff9353cfda5_377385_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_095406.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_095406_hud3fa1909296c6fd6a4450c737cfea1ed_345360_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_101734.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_101734_hu581b5e0361a7a568f5d67fea7e8c50cf_299853_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>I used some hot glue to act as light pipes and 2 black screw to extend the buttons soldered on the PCB</p>
<h2 id="installation">
    Installation 
    
    <a class="header-link" href="#installation">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_134025.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_134025_hu126e85c836e6da2aa7a857675bb8ada4_222985_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_134138.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_134138_hu5979d6f25c316f8da1a34af588cc3123_285550_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_134245.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_134245_hu1dd913efeaf585d100d897bff35cdc18_198675_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_134528.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_134528_hubb94eedc0f2741442e4798269926031b_244663_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="demo">
    Demo 
    
    <a class="header-link" href="#demo">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>And here is a little demo video of the whole system

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/gtHWWjUOjHU" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>
</p>
<h2 id="more-photos">
    More photos 
    
    <a class="header-link" href="#more-photos">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210212_214219.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210212_214219_hu355de701ca25c00fd84e91e96c6861ba_668598_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_165641.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_165641_huc18ffd4c6efe26c4da5219c287ec42b6_151724_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_175909-2.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_175909-2_hufd8e87744da797ca935877fdc7fe72df_253069_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210209_175928.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210209_175928_huc3d74937bedc1f98bd8044702ed661dc_303565_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210212_214051.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210212_214051_hue8dd77756bf58222f560e25732907edb_299580_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210212_214143__01-2.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210212_214143__01-2_hu521e44da852b74e2dcf9310e57951d08_255648_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210224_162434.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210224_162434_hu1ed051d3e95494d2ff52c65018f44f38_416339_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_121605.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_121605_hu82fcd7f83e0033d18b12f7fe53854313_274100_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210225_125027.jpg">
                            <img src="/bridging-the-gap-between-old-and-new-wifi-garage-door-controller/images/dl_IMG_20210225_125027_hub93ca681286fccefb739ae6c0b729062_248682_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<h2 id="links--docs">
    Links / Docs 
    
    <a class="header-link" href="#links--docs">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Arduino's code / 3d files / schematic / PCB: <a target="_blank" href="https://github.com/TheStaticTurtle/GarageDoorController">https://github.com/TheStaticTurtle/GarageDoorController</a></p>
<p>Garage door documentation: <a target="_blank" href="https://www.bft.cz/privat/navody/poh_sekcni/en/TIR%2060-120.pdf">https://www.bft.cz/privat/navody/poh_sekcni/en/TIR 60-120.pdf</a></p>
<p>Again, thanks to <a target="_blank" href="https://www.pcbway.com/">pcbway</a> for letting me try out their PCB manufacturing for this project.</p>

 ]]></content:encoded></item><item><title>Getting started with HID and the Pi Pico</title><description> &lt;p>Using the Raspberry Pi Pico and circuit python with a joystick to control the computer mouse&lt;/p></description><link>https://blog.thestaticturtle.fr/getting-started-with-hid-and-the-pi-pico/</link><guid>https://blog.thestaticturtle.fr/getting-started-with-hid-and-the-pi-pico/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 05 Feb 2021 10:35:06 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/getting-started-with-hid-and-the-pi-pico/images/cover_hub26a8002f6f6ccd8e05e3d25559bbb5b_497456_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/getting-started-with-hid-and-the-pi-pico/images/cover_hub26a8002f6f6ccd8e05e3d25559bbb5b_497456_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Getting started with HID and the Pi Pico</h1>
<span class="subtitle"><p>Using the Raspberry Pi Pico and circuit python with a joystick to control the computer mouse</p></span>
<br>

    <img class="" src='/getting-started-with-hid-and-the-pi-pico/images/cover_hub26a8002f6f6ccd8e05e3d25559bbb5b_497456_1350x900_fit_q80_box.jpg' alt="Getting started with HID and the Pi Pico"/>

<hr>

<p>A few days ago I bought a <a target="_blank" href="https://www.raspberrypi.org/products/raspberry-pi-pico/">Raspberry Pi Pico</a> the first thing I did was to flash micropython on it.</p>
<p>The process was quite simple, press the button, plug it in and transfer the file. After playing a bit with the REPL interpreter I was left with two Picos and no project idea.</p>
<p>So I searched a bit on the internet and I found that the RP2040 supports HID, so I started to look in that direction. As far as I looked the micropython firmware provided by the Raspberry Pi foundation did provide hid support, So I look for alternative.</p>
<p>Fortunately, <a target="_blank" href="https://circuitpython.org/board/raspberry_pi_pico/">Adafruit Circuit Python</a> is compatible with the Pi Pico and that one supported hid as some other thing (like showing up as a drive to transfer code). So I did the updating procedure again but with this firmware.</p>
<p>I then got the <code>adafruit_hid</code> folder from this GitHub repo</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/adafruit/Adafruit_CircuitPython_HID" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/2c396c968267b3f300ed7cdbf7deca27_hu0a6772dc640c693fadd7ca48c015cbfb_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/2c396c968267b3f300ed7cdbf7deca27_hu0a6772dc640c693fadd7ca48c015cbfb_0_0x720_resize_q90_h2_box_3.webp' alt='USB Human Interface Device drivers. Contribute to adafruit/Adafruit_CircuitPython_HID development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/adafruit/Adafruit_CircuitPython_HID">GitHub - adafruit/Adafruit_CircuitPython_HID: USB Human Interface Device drivers.</a>
        
        
            <p>USB Human Interface Device drivers. Contribute to adafruit/Adafruit_CircuitPython_HID development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    

<p>and dragged it onto the pico storage. I then started a terminal and jumped into the REPL interpreter a few lines of code later. A few lines of code later I was ready to go:</p>
<pre><code>import usb_hid
from adafruit_hid.mouse import Mouse

mouse = Mouse(usb_hid.devices)
mouse.move(10,0)
</code></pre>
<p>I then proceeded to solder a joystick to it following this very simple schematic:

    <figure>
        <a target="_blank" href="images/dl_image-1.png" >
            <img alt="" src="/getting-started-with-hid-and-the-pi-pico/images/dl_image-1_hue5894d0d1f5a9d4ea52e3f0e04b357b4_140147_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


And wrote some more code:</p>
<pre><code>import board
from analogio import AnalogIn
import usb_hid
from adafruit_hid.mouse import Mouse

mouse = Mouse(usb_hid.devices)
xAxis = AnalogIn(board.A1)
yAxis = AnalogIn(board.A0)

in_min,in_max,out_min,out_max = (0, 65000, -5, 5)
filter_joystick_deadzone = lambda x: int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min) if abs(x - 32768) &gt; 500 else 0


while True:
    x_offset = filter_joystick_deadzone(xAxis.value) * -1 #Invert axis
    y_offset = filter_joystick_deadzone(yAxis.value)
    mouse.move(x_offset, y_offset, 0)
</code></pre>
<p>The first 5 lines are for importing the necessary modules to read the analog signals and control the mouse.</p>
<p>The next 3 lines are declaring the objects representing the mouse and the two analog pins</p>
<p>Next is a lambda function, in short this creates a function named filter_joystick_deadzone which will return 0 if the joystick is in +/- 500 of the center value (32768) or else it will convert the value to a range of -5 to 5</p>
<p>Next is a while loop that reads the inputs, pass them to the filter and move the mouse</p>
<p>Here is a quick demo video:</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/ovAQQRtYmho" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>

 ]]></content:encoded></item><item><title>Wireless audio part 1 - 1st prototype</title><description> &lt;p>Making the first prototype of my wireless audio system using Ti's CC85XX series chips&lt;/p></description><link>https://blog.thestaticturtle.fr/wiau-part1-1stprototype/</link><guid>https://blog.thestaticturtle.fr/wiau-part1-1stprototype/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 22 Jan 2021 17:19:24 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/wiau-part1-1stprototype/images/cover_hu8acef0f4899aeaec1cab56f0b6f4e2d1_308099_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/wiau-part1-1stprototype/images/cover_hu8acef0f4899aeaec1cab56f0b6f4e2d1_308099_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Wireless audio part 1 - 1st prototype</h1>
<span class="subtitle"><p>Making the first prototype of my wireless audio system using Ti's CC85XX series chips</p></span>
<br>

    <img class="" src='/wiau-part1-1stprototype/images/cover_hu8acef0f4899aeaec1cab56f0b6f4e2d1_308099_1350x900_fit_q80_box.jpg' alt="Wireless audio part 1 - 1st prototype"/>

<hr>

<p>I'll be writing this as things come, so the writing style might be a bit different.</p>
<h2 id="introduction">
    Introduction 
    
    <a class="header-link" href="#introduction">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>For headphones, I own a Sennheiser HD-25. Frankly it's a superb piece, the audio is wonderful. However, I wanted for ages now to have a reliable and non-bulky way to make these headphone wireless with compromising quality too much.</p>
<p>I could obviously just a cheap UHF tx/rx pair but not only I would expose my audio to anyone but there would be no way to have the second feature that I wanted: a stereo back channel (or at least mono) for a microphone or line in.</p>
<p>After countless hours of search, let me introduce the CC85XX series, these chips from Texas Instruments are capable of juggling between 4 channels, and they work at 2.4GHz (granted the range might be reduced a bit but at least not anyone will be able to decode the audio). These chips are beasts:</p>
<ul>
<li>CC852x supports up to 2 channels</li>
<li>CC853x supports up to 4 channels</li>
<li>CC85x1 supports USB</li>
<li>32 to 48kHz</li>
<li>Minimum latency of 10ms (max of around 30)</li>
</ul>
<p>I was sold, so I started to recreate the convenient application circuit of the datasheet on easyeda:

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="Ti application circuit" src="/wiau-part1-1stprototype/images/dl_image.png" />
        </a>
        
    </figure>


Few tweaks that I did is replacing the antenna matching circuit with the 2450BM15A0002E which is a prebuild circuit for the CC chips.</p>
<p>And as I wanted to be as flexible as possible I placed both the matching circuit and the CC2590 rf amp on the schematic.</p>
<p>After spending way too much time trying to understand how the PurePath wireless configurator worked I finally found the audio codec chip that is compatible with my needs, and so I settled for the TLV320AIC3204IRHBR this chip is equally a beast, but I had one issue:</p>
<p>I could not figure out how to programmatic switch between the line in and the microphone input of the chip which was somewhat problematic. Sure I could reprogram the chip each time I want to switch but that didn't seem very viable, so now I had to mess with one of the thing I dread the most: analog signals.</p>
<p>After quite a bit of experimentation with the chips I had (tl07x, tl08x, lm386, lm358, ne5532) I made a circuit with the tl071 which somewhat worked, however I had to max out my sound card input amp to have something useable (mind you the quality was fantastic). So clueless as I am with analog signals I went for a search on the internet and found the OPA2348 sure they were way better options, but they were also a lot more expensive (I'm looking at you MAX4466).</p>
<p>Conveniently the OPA2348 data sheet provides an application schematic for and electret microphone preamp circuit, so I continued the schematic by recreating the circuit, however something caught my eyes: the application schematic states a band pass of 300Hz to 3kHz which is good but not quite good enough especially in the high frequency side</p>
<p>I then proceeded by creating a schematic for all of this

    <figure>
        <a target="_blank" href="images/dl_Schematic_CC8531_Breakout_2020-12-28_16-37-34___.png" >
            <img alt="1st prototype schematic" src="/wiau-part1-1stprototype/images/dl_Schematic_CC8531_Breakout_2020-12-28_16-37-34____hu30580712c79824629659035666ad37df_1571985_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


It's quite messy, but it's the first prototype I've placed a lot of test points to test everything, but it will never work the first time.</p>
<p>I next designed the PCB

    <figure>
        <a target="_blank" href="images/dl_image-1.png" >
            <img alt="1st prototype rendering" src="/wiau-part1-1stprototype/images/dl_image-1_hucbc06e3bc480d711da1ae0106f6cc625_163120_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


Like the schematic I'm bound to have made some mistake, but it should be alright.</p>
<hr>
<p>So after waiting a week for the PCB to arrive I for the first time soldered the component with solder paste. The USB-C connector was definitively the hardest to solder correctly, even now I'm not sure.</p>
<p>Obviously the first power up didn't work at all. Like VCC an GND shorted together. After many hours of scratching my head looking at the PCB and the schematic I could find the flaw. Turns out I might have meddled with the PCB after the order because you can see the VCC trace going straight into the ground plane

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="Beautiful VCC going straight into the ground plane" src="/wiau-part1-1stprototype/images/dl_image-2.png" />
        </a>
        
    </figure>


And well after cutting that and re-soldering the trace correctly it worked just fine.</p>
<p>Now the only issue is that I don't have a cc-debuger, so I ordered one but the mail service here SUCKS, so I wasn't able to do anything with the PCB for around 1.5 weeks. After I received it tho I was able to use the Ti PurePath Wireless configurator to program the chip which,</p>
<hr>
<p>So a few weeks back I got my cc-debugger program both boards both lighted up but didn't pair. And after searching for a config option that I would have missed I kinda gave up for 2-3 days, but then I remembered that I got a hackrf that can see where the signal is and while programming both boards as masters the signal wasn't in the same place.</p>
<p>So I started a thread on Ti E2E forum:

    
        

        
            








    


<blockquote class="embed embed-e2e_ti_com">
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://e2e.ti.com/support/wireless-connectivity/other-wireless/f/667/p/968243/3582181">CC8531: Failing to pair</a>
        
        
    </div>
</blockquote>
        
    

As per comment #3582181 :</p>
<p>The first board is working fine, it was a few hundred kHz of, so I adjusted the C1,C2 caps to 24pF instead of 16pf, and it's now only 3kHz off the test frequency.

    <figure>
        <a target="_blank" href="images/dl_SDRSharp_2021-01-09_13-23-56.png" >
            <img alt="SDR# Screenshot of the working board TX carrier" src="/wiau-part1-1stprototype/images/dl_SDRSharp_2021-01-09_13-23-56.png" />
        </a>
        
    </figure>


Second board is another story, on any channel it's almost always 15Mhz away from the desired test frequency. Moreover, it drifts on startup and drifts with temperature (like a lot).

    <figure>
        <a target="_blank" href="images/dl_SDRSharp_2021-01-09_13-13-55.png" >
            <img alt="SDR# Screenshot of the bad board TX carrier (See the peak isn&#39;t on the red line)" src="/wiau-part1-1stprototype/images/dl_SDRSharp_2021-01-09_13-13-55.png" />
        </a>
        
    </figure>


I tried swapping the crystal because you never know you never know and re-soldering the chip and in both case it did not change a thing. I'm more and more thinking that my CC8531 is dead on this board.</p>
<hr>
<p>And my suspicion were right I've soldered a new CC8531, configured everything, and they paired with each other. SUCCESS</p>
<p>Well not yet, they paired without any issues and I even managed to use the remote control option and control my computer volume however there was no sound to be heard</p>
<p>So make it easier to debug I soldered a TLV320AIC3204 to a QFN breakout board, painfully soldered jumper wires to the PCB pads and to the breakout board. I then started probing with my oscilloscope and sure enough there was no data on the I2S data lines, the clocks were working fine tho

    <figure>
        <a target="_blank" href="images/dl_IMG_20210118_160230---Copie.jpg" >
            <img alt="The painfully soldered wires &amp;amp; the breakout board" src="/wiau-part1-1stprototype/images/dl_IMG_20210118_160230---Copie_hu726dcd1a5eca8c117d20978ea492eb84_986411_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


I even started a new thread on ti e2e forum because I could not figure it out</p>
<p>
    
        

        
            








    


<blockquote class="embed embed-e2e_ti_com">
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://e2e.ti.com/support/wireless-connectivity/other-wireless/f/667/t/971755">CC8531: No data on the I2S data lines</a>
        
        
    </div>
</blockquote>
        
    

After more probing I managed to see that the codec chip was configured correctly since it outputted data:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/VXO6GsQL38I"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>
        
    

<p>As it turns out the guy who help me couldn't find an issue with my schematic and directed me toward a config issue. After discovering that there was preconfigured examples of project files and after doing a bit of tweaking to said files, I managed to get a 2 channel stereo link. Yay.

    <figure>
        <a target="_blank" href="images/dl_IMG_20210121_031500.jpg" >
            <img alt="Yeah, I know no video sorry" src="/wiau-part1-1stprototype/images/dl_IMG_20210121_031500_hu8acef0f4899aeaec1cab56f0b6f4e2d1_308099_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


There were issues tho the most annoying one being that I couldn't go further than 1 meter and other issues like the TLV320AIC3204 that only worked on the breakout board.</p>
<p>After much tinkering with the config file I was able to add a third channel for a microphone, and it worked. Not well, the audio cut a lot (tho I manged to keep it stable for 5min) but it showed that it was possible.</p>
<p>I suspect that a lot of my issues are related to my poor PCB design and component choices for the rf parts that's why I'm currently recreating a schematic for a new prototype, and I'll hopefully don't make any big mistakes. I'll do another blog post about it whenever I get finished everything</p>

 ]]></content:encoded></item><item><title>Stop now!! (Freebox parental api)</title><description> &lt;p>Using the freebox API to control a profile internet access with a big red button to toggle it (Oh and also rgb)&lt;/p></description><link>https://blog.thestaticturtle.fr/stop-now-freebox-parental-api/</link><guid>https://blog.thestaticturtle.fr/stop-now-freebox-parental-api/</guid><category> Diy</category><category> Electronics</category><category> Iot</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 01 Jan 2021 02:54:12 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/stop-now-freebox-parental-api/images/cover_hu22f011903ffe64621e246c7d3f109238_320646_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/stop-now-freebox-parental-api/images/cover_hu22f011903ffe64621e246c7d3f109238_320646_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Stop now!! (Freebox parental api)</h1>
<span class="subtitle"><p>Using the freebox API to control a profile internet access with a big red button to toggle it (Oh and also rgb)</p></span>
<br>

    <img class="" src='/stop-now-freebox-parental-api/images/cover_hu22f011903ffe64621e246c7d3f109238_320646_1350x900_fit_q80_box.jpg' alt="Stop now!! (Freebox parental api)"/>

<hr>

<p>For a while now I wanted to have a button that I could use to mainly troll my little brother while he's gaming.</p>
<p>There are multiple ways to go about it, I could implement an arpspoof to change the gateway address, or I could go the much easier way and just use the router from my ISP (Freebox from Free) API to control a profile internet access's.</p>
<p>First the freebox API documentation sucks first link you get when you google it is <a target="_blank" href="https://dev.freebox.fr/sdk/os/">https://dev.freebox.fr/sdk/os/</a> however, this is the v4 documentation and mine uses v8. Following the v4 worked great until I had to actually control the status and not just monitor it. After many hours of searching, the latest documentation can be found in the help menu on the configuration interface.</p>
<p>Otherwise, using the api was really straight forward, maybe the login part was a little confusing but apart that it was all good.</p>
<p>Next I need to put all this on an esp8266, as I did all the testing with python and never tried using micropython, I flashed it on the esp and started porting the python test code.</p>
<p>And it went downhill from there. To simplify things I removed the &quot;acquire app token&quot; part and add a variable for it.</p>
<p>To get a session token, you need to get a challenge and do an hmac-sha1 with it and the token. And after many hours of trying, the micropython hmac, hashlib, uhashlib were not able to give me a hmac. After these painful hours I just gave up and searched for a pure python sha1 implementation and after copying some part of hmac and fixing missing functions I had it working nicely.</p>
<p>After all that it wasn't really difficult, add prints everywhere, add the ws2812 for the status indication and add the button.

    <figure>
        <a target="_blank" href="images/dl_IMG_20201231_175955.jpg" >
            <img alt="" src="/stop-now-freebox-parental-api/images/dl_IMG_20201231_175955_hu5f900e707cc74231e48869a0079fe343_333566_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


But I couldn't really leave this mess hanging around (I use the keypad as one big button, useless otherwise as it isn't a matrix).</p>
<p>So after some time I managed to make a good enough box

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/stop-now-freebox-parental-api/images/dl_image.png" />
        </a>
        
    </figure>


After way to much time 3d printing and assembly I managed to fit everything nicely.</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210101_014249.jpg">
                            <img src="/stop-now-freebox-parental-api/images/dl_IMG_20210101_014249_hu590fb9beefb42ad6f73d8e100db464e9_428795_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210101_024907.jpg">
                            <img src="/stop-now-freebox-parental-api/images/dl_IMG_20210101_024907_hu8184aab475b2c7a5c870a012e7cf7634_301277_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210101_024324.jpg">
                            <img src="/stop-now-freebox-parental-api/images/dl_IMG_20210101_024324_hu23d483b7a5a85588b3ffefa0d84fb8bd_217233_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210101_024339.jpg">
                            <img src="/stop-now-freebox-parental-api/images/dl_IMG_20210101_024339_hu8901aa98567fc91ea11b97335f378a88_189209_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20210101_024829.jpg">
                            <img src="/stop-now-freebox-parental-api/images/dl_IMG_20210101_024829_huab6e0d602f1cf7b9b94116f5d8f30962_183120_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>The ring get white/blue at boot while connecting to Wi-Fi, purplish while connection to the API and red/green depending on if the profile has internet access or not. Also, when you press the button the ring goes white while it does the request.</p>
<p>The parental control system is actually good for once, when a &quot;pause&quot; is scheduled, you can't have internet access and pressing the button can't turn it back on. When in normal operation, pressing the button will simply toggle the override of the interface.</p>
<p>I've also added a config file to disable the button if for example I switch the profile off and someone takes it they can't turn it back on without the router password</p>
<p>All in all the button works really well and could probably be used for something else than just for trolling my brother. Micropython was also a nice experience, but it's definitely not for the esp8266 during debugging I frequently had some unable to allocate memory, the esp32 however didn't have any issues. Micropython also lack a really good code editor because uPyCraft sucks compared to PyCharm or even VS Code</p>
<p>As always, here is the code I used (Also includes 3d models)</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/StopNowButton" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/1016c5c72aa0849e31e8df87e1f50548_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/1016c5c72aa0849e31e8df87e1f50548_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Hardware button to control the freebox profiles api  - GitHub - TheStaticTurtle/StopNowButton: Hardware button to control the freebox profiles api'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/StopNowButton">GitHub - TheStaticTurtle/StopNowButton: Hardware button to control the freebox profiles api</a>
        
        
            <p>Hardware button to control the freebox profiles api  - GitHub - TheStaticTurtle/StopNowButton: Hardware button to control the freebox profiles api</p>
        
    </div>
</blockquote>

 ]]></content:encoded></item><item><title>Dirt cheap DIY USB Camera</title><description> &lt;p>A little after that every blog was popping with the uvc-gadget script to turn your pi4/pi0 into a high quality USB camera. [...] And so at 2am it was decided I was making a webcam&lt;/p></description><link>https://blog.thestaticturtle.fr/dirt-cheap-diy-usb-camera/</link><guid>https://blog.thestaticturtle.fr/dirt-cheap-diy-usb-camera/</guid><category> Diy</category><dc:creator> Samuel</dc:creator><pubDate>Wed, 16 Dec 2020 15:24:42 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/dirt-cheap-diy-usb-camera/images/cover_hu38762749d939349d6e07717fd140ab52_563851_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/dirt-cheap-diy-usb-camera/images/cover_hu38762749d939349d6e07717fd140ab52_563851_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Dirt cheap DIY USB Camera</h1>
<span class="subtitle"><p>A little after that every blog was popping with the uvc-gadget script to turn your pi4/pi0 into a high quality USB camera. [...] And so at 2am it was decided I was making a webcam</p></span>
<br>

    <img class="" src='/dirt-cheap-diy-usb-camera/images/cover_hu38762749d939349d6e07717fd140ab52_563851_1350x900_fit_q80_box.jpg' alt="Dirt cheap DIY USB Camera"/>

<hr>

<p>A while ago I have seen all the excitement over the pi camera HQ but at around €50 it was still a bit too expensive to justify buying it just because I could. Shortly after that of course the pandemic hit and there was a massive shortage of USB cameras. A little after that every blog was popping with the uvc-gadget script to turn your pi4/pi0 into a high quality USB camera.</p>
<p>I have to agree that for the price of around €60 you could get a superb USB camera compared to the other options on Amazon for example. (That was still too expensive for me mind you)</p>
<p>I had a pi0 lying around for a while now, an old pi camera v1 that bought out of AliExpress for really cheap (it was like 5-7€) I also had an old 70 mm CCTV camera lens from old projects.</p>
<p>And so at 2am it was decided I was making a webcam, the software part was really simple, install Raspbian and install the uvc-gadget. I personally followed this tutorial:</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://www.jeffgeerling.com/blog/2020/raspberry-pi-makes-great-usb-webcam-100">https://www.jeffgeerling.com/blog/2020/raspberry-pi-makes-great-usb-webcam-100</a>
            </div>
        </blockquote>
    

<p>Now having everything (cables and all) hanging around isn't pretty, so I spend some time and design a nice case for everything</p>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2020-12-16_13-36-59.png">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_SLDWORKS_2020-12-16_13-36-59.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_SLDWORKS_2020-12-16_13-37-34.png">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_SLDWORKS_2020-12-16_13-37-34.png" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>After some 15h of printing here's what I got:</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201215_165508-1.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201215_165508-1_huffd4f6f6c30f8dfcf926926dd42ef702_231702_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201215_190526-1.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201215_190526-1_hua55bc8ce3e6e1ab5f664477789093c0a_886696_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201215_191925.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201215_191925_hu96368a9e1f490eb7c82e4ecd4836a074_800986_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>After struggling quite a bit to glue, solder and fix everything in place I had this wonderful case:</p>


    
    

    
    
        
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201215_191937.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201215_191937_hu1ed54be35cbf7e24a95b3fcffb7b07a0_611170_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201216_023114.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201216_023114_huba969dc4d22f6e4f347ef1cc6a80cc23_557280_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_IMG_20201216_023022.jpg">
                            <img src="/dirt-cheap-diy-usb-camera/images/dl_IMG_20201216_023022_hu0610f05c96de057872d5e8cf4c835c17_662040_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>Works pretty great, the pi0 get a little warm hence I left the heat sink apparent on the backside. I also left a groove to put some addressable LEDs to light up whatever I'm recording (haven't received it yet).</p>
<p>The little jig that I 3d printed to adjust the assembly works pretty well, I would however, should have it a little more thick, tends to bend a little. I tested it on my microphone stand where I can do stupid things like put it +2m high.</p>
<p>Here's my &quot;BOM&quot;</p>
<table>
<thead>
<tr>
<th>Item</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>Pi0</td>
<td>5€</td>
</tr>
<tr>
<td>PiCamera V1 + Pi0 Flex cable</td>
<td>~8€</td>
</tr>
<tr>
<td>Random screw found in my drawer</td>
<td>Free</td>
</tr>
<tr>
<td>Filament</td>
<td>~100g</td>
</tr>
<tr>
<td>Lens</td>
<td>Free (Found one for ~10€ tho)</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>~25€</strong></td>
</tr>
</tbody>
</table>
<p>All in all I think this camera is great if you're in a pinch but isn't ideal, the pi camera v2 would be a better choice for example and maybe a better lens.</p>
<p>However, considering the cost I can't find a real alternative with 1080p 30fps with a 70 mm zoom.</p>
<p>This is a really short post, but it's the first one in the camera space for which I have a <strong>lot</strong> more ideas that I'll probably write and the introduction of uvc-gadget helped me quite a bit for the next camera project. This space is evolving a lot, so it might be awhile until the next camera post.</p>

 ]]></content:encoded></item><item><title>Auto mute while in meetings</title><description> &lt;p>Making my door automatically mute me when it gets open so that I don't disturb my classes&lt;/p></description><link>https://blog.thestaticturtle.fr/auto-mute-while-in-meetings/</link><guid>https://blog.thestaticturtle.fr/auto-mute-while-in-meetings/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 03 Nov 2020 16:28:44 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/auto-mute-while-in-meetings/images/cover_huf0e0cd7be229ef465bb52ae937b07fff_278267_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/auto-mute-while-in-meetings/images/cover_huf0e0cd7be229ef465bb52ae937b07fff_278267_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Auto mute while in meetings</h1>
<span class="subtitle"><p>Making my door automatically mute me when it gets open so that I don't disturb my classes</p></span>
<br>

    <img class="" src='/auto-mute-while-in-meetings/images/cover_huf0e0cd7be229ef465bb52ae937b07fff_278267_1350x900_fit_q80_box.jpg' alt="Auto mute while in meetings"/>

<hr>

<p>A few days ago the French government announced a lockdown, universities had to close and do their classes online.</p>
<p>As I live with my family, even if I have a room to myself my parent sometimes barges in saying something thing like &quot;Do you want some pie&quot; while I'm unmuted and in class, last night I had enough, so I made this project.</p>
<h2 id="hardware">
    Hardware 
    
    <a class="header-link" href="#hardware">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>After some reflexion I choose to use and Attiny85 with the digispark bootloader, a very long 2pin(actually 3) cable and magnetic sensor.</p>
<p>First I managed to stick the sensor to my door:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20201103_120240.jpg" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_IMG_20201103_120240_hu99c22280345611a308a210f23e68524d_160568_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20201103_120248.jpg" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_IMG_20201103_120248_hu5c3ad0641d989682351458d5ca93a94e_183284_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>It's not very pretty but it works well enough.</p>
<p>Next was the attiny, unfortunately I couldn't find an original digispark, so I used one of my V-USB breakout</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2020-11-03_12-32-44.png" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_chrome_2020-11-03_12-32-44.png" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_chrome_2020-11-03_12-33-19.png" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_chrome_2020-11-03_12-33-19.png" />
        </a>
        
    </figure>

</p>
<p>And a sop-8 breakout board cut to only have the 4 pins on each side. Fortunately the soldering was very simple, after adding a pull down resistor and soldering a 3.5 mm jack between PB0 and +5V the electronics side was finished:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20201102_214804.jpg" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_IMG_20201102_214804_hu796933297b5ebf5dafb5b5605de868bb_621907_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20201102_214810.jpg" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_IMG_20201102_214810_hu5a438892b6e005b49c81e3541cb67813_576340_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20201103_120318__01.jpg" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_IMG_20201103_120318__01_huf0e0cd7be229ef465bb52ae937b07fff_278267_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="software">
    Software 
    
    <a class="header-link" href="#software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>First I want to say that I use VoiceMeeter Patato to route my audio, as I use two computers with the mouse synced (See synergy) I also used Voicemeeter VBAN protocol to stream audio in real time between the computers

    <figure>
        <a target="_blank" href="images/dl_voicemeeter8_2020-11-03_13-05-31.png" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_voicemeeter8_2020-11-03_13-05-31_hu20ef23c904081aa2b6b6a0443659e813_172162_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


I didn't to just have the digispark act as a serial port, so I choose the keys combo CTRL + ALT +F2 to mute myself and CTRL + ALT +F3 to unmute. So all in all the attiny code is very tiny</p>
<pre><code>#include &quot;DigiKeyboard.h&quot;

void setup() {
  pinMode(PB0,INPUT);
}

void loop() {
  DigiKeyboard.delay(5);
  if(!digitalRead(PB0)) {
    DigiKeyboard.sendKeyStroke(0);
    DigiKeyboard.sendKeyStroke(KEY_F2, MOD_ALT_LEFT | MOD_CONTROL_LEFT);
    while(!digitalRead(PB0)) { DigiKeyboard.delay(50); }
    DigiKeyboard.sendKeyStroke(KEY_F3, MOD_ALT_LEFT | MOD_CONTROL_LEFT);
  }
}
</code></pre>
<p>Next was the software on the computer I could easily just have bound the keys onto VoiceMeeter and call it a day but I wanted to have an indicator of when the switch was muting my mic</p>
<p>The VB-AUDIO software is fantastic, like realy wonderful. It can be controlled via keystrokes but also via the VBAN Text sub-protocol (See the full documentation here <a target="_blank" href="https://www.vb-audio.com/Voicemeeter/VBANProtocol_Specifications.pdf">https://www.vb-audio.com/Voicemeeter/VBANProtocol_Specifications.pdf</a>). So instead of send keystrokes everywhere and preventing me to write on the computer I decided to use the sub-protocol</p>
<p>As I suck in making &quot;art&quot; I took a microphone SVG from flaticon (<a target="_blank" href="https://www.flaticon.com/free-icon/microphone-black-shape_25682?term=mic&amp;amp;page=1&amp;amp;position=2">here</a>).</p>
<p>First I sized and colored the window correctly, load the svg, register the key bindings and add an exit menu:</p>
<pre><code>private void Form1_Load(object sender, EventArgs e) {
	this.TransparencyKey = (BackColor);
	svgImage.Location = new Point(0, 0);
	this.Size = svgImage.Size;

	svgDoc = SvgDocument.Open(&quot;C:\\Users\\tugle\\Downloads\\microphone-black-shape.svg&quot;);
	recolorSVGDoc(svgDoc.Descendants(), new SvgColourServer(Color.FromArgb(255, 255, 64, 0)));
	svgImage.Image = svgDoc.Draw();
	timer1.Enabled = true;

	// ALT+CTRL = 1 + 2 = 3 , CTRL+SHIFT = 2 + 4 = 6...
	RegisterHotKey(this.Handle,   MUTE_HOTKEY_ID, 3, (int)Keys.F2); ;
	RegisterHotKey(this.Handle, UNMUTE_HOTKEY_ID, 3, (int)Keys.F3); ;

	this.notifyIcon1.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
	this.notifyIcon1.ContextMenuStrip.Items.Add(&quot;Exit&quot;, null, this.MenuExit_Click);
}
</code></pre>
<p>While the <code>muted</code> variable is true the windows is constantly set to visible and set to the TopMost window (in front of every other one), this varaiable is also linked to a timer that makes the opacity of the window fade to add some style</p>
<p>Next I made these function that are executed when I want the program to mute/unmute me</p>
<pre><code>void oneShotMute() {
	Console.WriteLine(&quot;MUTE&quot;);
	muted = true;
	vbt.send(&quot;Strip(0).Mute = 1&quot;);
}
void oneShotUnMute() {
	Console.WriteLine(&quot;UNMUTE&quot;);
	muted = false;
	vbt.send(&quot;Strip(0).Mute = 0&quot;);
}
</code></pre>
<p>As my mic is on the first VoiceMeter input the correct command to send is <code>Strip(0).Mute = x</code>

    <figure>
        <a target="_blank" href="images/dl_voicemeeter8_2020-11-03_13-06-33.png" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_voicemeeter8_2020-11-03_13-06-33.png" />
        </a>
        
    </figure>


The vbt variable is an instance of the VBAN Text class that I made. Since I don't need things to be flexible with this app I hard coded the values like the stream name and skipped any security checks like the command length which has a maximum length and the end the function looks like this</p>
<pre><code>public void send(String text) {
	char[] send_buffer = {
		'V','B','A','N',
		(char)0x40,
		(char)0x00,
		(char)0x00,
		(char)0x01,
		'C','o','m','m','a','n','d','1','\0','\0','\0','\0','\0','\0','\0','\0',
		'\0','\0','\0','\0'
	};

	sock.SendTo(
		Encoding.ASCII.GetBytes(
			((new string(send_buffer)) + text).ToCharArray()
		)
	, endPoint);
}
</code></pre>
<p>After a lot of coding to make sure that I could move the icon by dragging it arround the screen and that everything worked as it was supposed to I clicked generate and it was done. Here is what it looks like:

    <figure>
        <a target="_blank" href="images/dl_2020-11-03_15-20-31.png" >
            <img alt="" src="/auto-mute-while-in-meetings/images/dl_2020-11-03_15-20-31_hua2f0fd83d7ce92b3188a22fed4ae18fd_148840_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h2 id="conclusion">
    Conclusion 
    
    <a class="header-link" href="#conclusion">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>True, I could have just used an esp8266 to send the UDP packet to VoiceMetter but I like using wires. Now the only issue that I have is since I share my keyboard using synergy between my computers my cursor is on my second computer the keystrokes aren't registered on my app and only sent to my second computer (which in itself is a good thing) so I'll need to write a patch for that.</p>
<p>As always here's the code (might differ from the article):</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/VoiceMeeterAutoMute" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/26610647facbc39c52c1e5ce0b55462e_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/26610647facbc39c52c1e5ce0b55462e_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/VoiceMeeterAutoMute development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/VoiceMeeterAutoMute">GitHub - TheStaticTurtle/VoiceMeeterAutoMute</a>
        
        
            <p>Contribute to TheStaticTurtle/VoiceMeeterAutoMute development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    

<h2 id="video">
    Video 
    
    <a class="header-link" href="#video">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Quick demo video:</p>

    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/OW962AcEFpI"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>

 ]]></content:encoded></item><item><title>HexClock: Redesigning time</title><description> &lt;p>Iterating over a pre-existing design to make it better and have cool clock&lt;/p></description><link>https://blog.thestaticturtle.fr/hexclock-redesigning-time/</link><guid>https://blog.thestaticturtle.fr/hexclock-redesigning-time/</guid><category> 3 d</category><category> Diy</category><category> Electronics</category><category> Iot</category><dc:creator> Samuel</dc:creator><pubDate>Fri, 25 Sep 2020 08:39:56 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/hexclock-redesigning-time/images/cover.png"/><media:content url="https://blog.thestaticturtle.fr/hexclock-redesigning-time/images/cover.png" medium="image"/><content:encoded><![CDATA[ <h1>HexClock: Redesigning time</h1>
<span class="subtitle"><p>Iterating over a pre-existing design to make it better and have cool clock</p></span>
<br>

    <img class="" src='/hexclock-redesigning-time/images/cover_hu8f245c1bf34f4944e1bdb525becdcaf8_199165_1350x900_fit_q80_bgffffff_box_3.jpg' alt="HexClock: Redesigning time"/>

<hr>

<p>First credit where credit is due. This isn't my idea I based my build around <a target="_blank" href="https://www.youtube.com/channel/UCFYguRGMmGpH493PDX5WmBA">Mukesh Sankhla</a> design:</p>
<p>However, I didn't like his design: way to thick and relatively small. I own a cr-10, so I opened SolidWorks and started by drawing a 30 cm hexagon.</p>
<p>I slimmed down his version to a 2.5 cm thick version and the result look like this:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_unknown.png" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_unknown.png" />
        </a>
        
    </figure>


(Before printing I added some fillets and rounded some edges)</p>
<p>After printing and removing a lot of support it looked like this:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_20200906_132520.jpg" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_20200906_132520.jpg" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20200906_123628.jpg" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_IMG_20200906_123628.jpg" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20200906_123632.jpg" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_IMG_20200906_123632.jpg" />
        </a>
        
    </figure>

</p>
<p>I then painfully soldered 96 ws2812b in each hole. As the power supply I'm using the only one I could find and that is an old PS2 power supply delivering 8.5v at 5amps, so I separated the clock into 3 sections with 3 buck converter to step it down to 5v.</p>
<p>I choose the esp8266 on a wemos d1 mini as a controller because it has Wi-Fi and it's great, however if I need more power I'll switch it to an esp32.</p>
<p>Next was coding, for me it was start of school again and I didn't want to bring what looked like a bomb at my university, so I created a python simulator that allowed me to create animation at school:

    <figure>
        <a target="_blank" href="images/dl_Screenshot_20200908_134225.png" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_Screenshot_20200908_134225.png" />
        </a>
        
    </figure>


Converting the python code base to c++ wasn't that hard and most of the coding for the esp was done on <a target="_blank" href="https://www.twitch.tv/thestaticturtle">twitch</a> with most of them saved here: <a target="_blank" href="https://www.youtube.com/playlist?list=PLgOTbM7xj9ggbPVsv--_13v2cieBVQZOv">https://www.youtube.com/playlist?list=PLgOTbM7xj9ggbPVsv--_13v2cieBVQZOv</a></p>
<p>After one week of looking at a non diffused piece of plastic I cut a plexiglass sheet and sanded the side to make a diffuser</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20200907_203913-1.jpg" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_IMG_20200907_203913-1.jpg" />
        </a>
        
    </figure>

</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_video.png" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_video.png" />
        </a>
        
    </figure>

</p>
<p>It still isn't perfect that's why I order some white acrylic samples to use as a diffuser and if one of them is better than what I have now I'll order a pre-cut hexagon. I might also experiment with putting some lenses on top of the LED to spread the light even more. But the best solution might just be to add a bit of thickness back to increase the currently ~5 mm space between the LED and the acrylic.</p>
<p>The clock connect to my Wi-Fi and respond to home-assistant via MQTT. I've set it up so that you can control the time and background part separately to allow sleeping next to the thing.

    <figure>
        <a target="_blank" href="images/dl_Screenshot_20200915_223139.png" >
            <img alt="" src="/hexclock-redesigning-time/images/dl_Screenshot_20200915_223139.png" />
        </a>
        
    </figure>


Apart from that there is still a few things that need to be improved like the power distribution when using the 96 LEDs at full white and write some more animation / static backgrounds. And one led with a brightness delta to fix.</p>
<p>As with most of my projects it's open source so here is the code:</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/HexClock" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/01f85266fec2984ddd678f7ed57c6a0e_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/01f85266fec2984ddd678f7ed57c6a0e_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='Software for the esp8266 running an hexagonal clock - TheStaticTurtle/HexClock'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/HexClock">GitHub - TheStaticTurtle/HexClock: Software for the esp8266 running an hexagonal clock</a>
        
        
            <p>Software for the esp8266 running an hexagonal clock - TheStaticTurtle/HexClock</p>
        
    </div>
</blockquote>

 ]]></content:encoded></item><item><title>CM118B: Adding usb audio to my projects</title><description> &lt;p>Needed USB audio input for my audio matrix. There wasn't an evaluation kit available, so I made one&lt;/p></description><link>https://blog.thestaticturtle.fr/cm118b-adding-usb-audio-to-my-projects/</link><guid>https://blog.thestaticturtle.fr/cm118b-adding-usb-audio-to-my-projects/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 27 Aug 2020 01:33:08 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/cm118b-adding-usb-audio-to-my-projects/images/cover_hu1dfbcb9bc4172255f6198f5f63ae6955_305847_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/cm118b-adding-usb-audio-to-my-projects/images/cover_hu1dfbcb9bc4172255f6198f5f63ae6955_305847_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>CM118B: Adding usb audio to my projects</h1>
<span class="subtitle"><p>Needed USB audio input for my audio matrix. There wasn't an evaluation kit available, so I made one</p></span>
<br>

    <img class="" src='/cm118b-adding-usb-audio-to-my-projects/images/cover_hu1dfbcb9bc4172255f6198f5f63ae6955_305847_1350x900_fit_q80_box.jpg' alt="CM118B: Adding usb audio to my projects"/>

<hr>

<p>In my everlasting quest to make a good audio matrix I wanted to add USB support for one or two modules.</p>
<p>After some quick searches I found the company CMedia that produces ICs that goes into sound cards, perfect. I had quite a few requirements:</p>
<ul>
<li>Stereo OUT and IN</li>
<li>Easy-ish to implement</li>
<li>Possibility to chnage the displayed device name (And why not the VIDs/PIDs)</li>
<li><strong>Cheap-ish</strong></li>
</ul>
<p>After comparing option I settled for the CM118B which met all the requirement and was available on LCSC. I first search for other hobbyist project using this chip and didn't found one so I had no idea how to get it working.</p>
<p>Sometimes chips companies will sell evaluation board to test the ic and figure things out. Unfortunately it wasn't the case here, so I created a project in EasyEDA, placed the IC and put test pads on every pin that was used for configuration (+some power ones) the configuration pins are these:
Pin #Symbol10PWRSEL13MODE38MICONLY39MSEL
At the end the schematic looked like this: (If you look closely you'll spot the mistake)

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/cm118b-adding-usb-audio-to-my-projects/images/dl_image.png" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_image-1.png" >
            <img alt="" src="/cm118b-adding-usb-audio-to-my-projects/images/dl_image-1.png" />
        </a>
        
    </figure>


After waiting one week and after starting to solder everything up I realized that I swapped the 1.8v and 3.3v lines (pin 12/13) after cutting the line and and soldering the right pads on the second 1.8v and 3.3v outputs it still didn't work.</p>
<p>Remember that at the time I thought that that was the only mistake so that why I went on and soldered the ic in place. Oh, boy I should have double-checked. See the traces underneath the ic ? See how they are connecting to pin ( 12 / 13 / 36 / 37) ? Well those pins are 1.8v / 3.3v / 3.6v / 1.8v and the 12 (3.3v) is connected to 37 (1.8v) so the IC wasn't gonna work shorting 3.3v to 1.8v.</p>
<p>Unfortunately I don't have a hot air station yet, so I had to make a tool out of copper wire to heat up all the pads at once. After that I managed to cut the traces and re-solder the ic.</p>
<p>Kudos to CMedia cause the IC survived at ~400°C for more than 5min and an internal regulator shorted to another one. After fixing everything my board was detected by my computer. After spending 15min trying to set the config pads correctly and messing a bit with the buttons everything worked sound was coming out and with some more messing with the mute button sound was coming in.</p>
<p>Things still on the to-do list is eliminating the annoying high-pitched buzz a low volume (It probably needs some isolation and probably another PCB maybe with an external i2s DAC/ADC maybe a whole other chip) and the last thing to do is program the EEPROM to set some other config bit that aren't accessible by physical pins (For example the display name that I will probably change to something like &quot;AudioMatrix #1 Input/Output&quot;)</p>
<p>At the end the PCB looked like this (minus the EEPROM + some power supply isolation tests):

    <figure>
        <a target="_blank" href="images/dl_image-2.png" >
            <img alt="" src="/cm118b-adding-usb-audio-to-my-projects/images/dl_image-2_hu872dfbc030c24bff85d874e0a9af0fc3_3611384_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>

 ]]></content:encoded></item><item><title>Creating a custom component for home assistant</title><description> &lt;p>So after building a reliable enough software for my open433 board, I wanted to make add my project as a platform for home assistant.&lt;/p></description><link>https://blog.thestaticturtle.fr/creating-a-custom-component-for-homeassistant/</link><guid>https://blog.thestaticturtle.fr/creating-a-custom-component-for-homeassistant/</guid><category> Diy</category><category> Homelab</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 18 Aug 2020 21:25:17 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/creating-a-custom-component-for-homeassistant/images/cover_hu4e1c60ab933093abec28c7e0dbf6c4e2_38478_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/creating-a-custom-component-for-homeassistant/images/cover_hu4e1c60ab933093abec28c7e0dbf6c4e2_38478_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Creating a custom component for home assistant</h1>
<span class="subtitle"><p>So after building a reliable enough software for my open433 board, I wanted to make add my project as a platform for home assistant.</p></span>
<br>

    <img class="" src='/creating-a-custom-component-for-homeassistant/images/cover_hu4e1c60ab933093abec28c7e0dbf6c4e2_38478_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Creating a custom component for home assistant"/>

<hr>

<p>So after building a reliable enough software for my open433 board, I wanted to make add my project as a platform for home assistant. However, I found official documentation by homeassistant.io very shitty when you don't know the inner workings of the platform so here's my take on it:</p>
<p>To create a home assistant component you need to create a folder named custom_components folder in the home assistant configuration directory (same one as the configuration.yaml) then in this older you need to create a folder with the name of your integration.</p>
<p>In this folder create a file name manifest.json this is where we will be declaring information about you integration:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span>{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span>  <span style="color:#309;font-weight:bold">&#34;domain&#34;</span>: <span style="color:#c30">&#34;open433&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span>  <span style="color:#309;font-weight:bold">&#34;name&#34;</span>: <span style="color:#c30">&#34;Open433 board&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span>  <span style="color:#309;font-weight:bold">&#34;documentation&#34;</span>: <span style="color:#c30">&#34;https://github.com/TheStaticTurtle/Open433&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span>  <span style="color:#309;font-weight:bold">&#34;requirements&#34;</span>: [<span style="color:#c30">&#34;pyserial==3.4&#34;</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span>  <span style="color:#309;font-weight:bold">&#34;codeowners&#34;</span>: [<span style="color:#c30">&#34;@TheStaticTurtle&#34;</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7</span><span>}
</span></span></code></pre></div><p>In here you specify the domain of your integration then the description the documentation of the project the requirements needed by you program (in my case pyserial) and then the GitHub usernames of who made the code</p>
<p>Then in the same folder create a file named <strong>init</strong>.py in this file you will need to declare the configuration needed by your integration. So first import some constants / libraries:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">logging</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">homeassistant.const</span> <span style="color:#069;font-weight:bold">import</span> EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">homeassistant.helpers.config_validation</span> <span style="color:#069;font-weight:bold">as</span> <span style="color:#0cf;font-weight:bold">cv</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">voluptuous</span> <span style="color:#069;font-weight:bold">as</span> <span style="color:#0cf;font-weight:bold">vol</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">threading</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">.</span> <span style="color:#069;font-weight:bold">import</span> rcswitch
</span></span></code></pre></div><p>in my case I have the library that I made in the same folder (named rcswitch.py). Then you need to declare some stuff like the domain (the name of your integration) , the name of the parameters used by the configuration and finally the configuration schema:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span>_LOGGER <span style="color:#555">=</span> logging<span style="color:#555">.</span>getLogger(__name__)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>DOMAIN <span style="color:#555">=</span> <span style="color:#c30">&#34;open433&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>CONF_COMPORT <span style="color:#555">=</span> <span style="color:#c30">&#34;port&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>CONF_COMSPEED <span style="color:#555">=</span> <span style="color:#c30">&#34;speed&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>REQ_LOCK <span style="color:#555">=</span> threading<span style="color:#555">.</span>Lock()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>CONFIG_SCHEMA <span style="color:#555">=</span> vol<span style="color:#555">.</span>Schema(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>	{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>		DOMAIN: vol<span style="color:#555">.</span>Schema({
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>			vol<span style="color:#555">.</span>Required(CONF_COMPORT): cv<span style="color:#555">.</span>string,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>			vol<span style="color:#555">.</span>Optional(CONF_COMSPEED, default<span style="color:#555">=</span><span style="color:#f60">9600</span>): cv<span style="color:#555">.</span>positive_int,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>		})
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>	},
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>	extra<span style="color:#555">=</span>vol<span style="color:#555">.</span>ALLOW_EXTRA,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>)
</span></span></code></pre></div><p>So for me the configuration could look like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#309;font-weight:bold">open433</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#bbb">  </span><span style="color:#309;font-weight:bold">port</span>:<span style="color:#bbb"> </span>COM3<span style="color:#bbb">
</span></span></span></code></pre></div><p>That's it for declaring the constants now you need to declare the actual setup function my one looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">setup</span>(hass, config):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	conf <span style="color:#555">=</span> config[DOMAIN]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>	comport <span style="color:#555">=</span> conf<span style="color:#555">.</span>get(CONF_COMPORT)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>	comspeed <span style="color:#555">=</span> conf<span style="color:#555">.</span>get(CONF_COMSPEED)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>	rf <span style="color:#555">=</span> rcswitch<span style="color:#555">.</span>RCSwitch(comport, speed<span style="color:#555">=</span>comspeed)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>	rf<span style="color:#555">.</span>libWaitForAck(<span style="color:#069;font-weight:bold">True</span>, timeout<span style="color:#555">=</span><span style="color:#f60">1</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">cleanup</span>(event):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>		rf<span style="color:#555">.</span>cleanup()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">prepare</span>(event):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>		rf<span style="color:#555">.</span>prepare()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>		rf<span style="color:#555">.</span>startReceivingThread()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>		hass<span style="color:#555">.</span>bus<span style="color:#555">.</span>listen_once(EVENT_HOMEASSISTANT_STOP, cleanup)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>	hass<span style="color:#555">.</span>bus<span style="color:#555">.</span>listen_once(EVENT_HOMEASSISTANT_START, prepare)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>	hass<span style="color:#555">.</span>data[DOMAIN] <span style="color:#555">=</span> rf
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>	<span style="color:#069;font-weight:bold">return</span> <span style="color:#069;font-weight:bold">True</span>
</span></span></code></pre></div><p>To break it down first we get the configuration from home assistant config for the current domain (line 2) then we retrieve the config (line3-4). Then specific to me I initialize my library (line6-7) then we define function for home assistant to execute while it start stops (line9-17) and then I store the instance of my library into home assistant data for the specific domain.</p>
<p>And that all for the <code>__init__.py</code></p>
<p>After all this you need to add the component for the platform as my project controls ac rf switches I wanted to make a switch component, to do that simply create a switch.py in the same folder</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">logging</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">voluptuous</span> <span style="color:#069;font-weight:bold">as</span> <span style="color:#0cf;font-weight:bold">vol</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">homeassistant.components.switch</span> <span style="color:#069;font-weight:bold">import</span> SwitchEntity, PLATFORM_SCHEMA
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">homeassistant.const</span> <span style="color:#069;font-weight:bold">import</span> CONF_NAME, CONF_SWITCHES
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#069;font-weight:bold">import</span> <span style="color:#0cf;font-weight:bold">homeassistant.helpers.config_validation</span> <span style="color:#069;font-weight:bold">as</span> <span style="color:#0cf;font-weight:bold">cv</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#069;font-weight:bold">from</span> <span style="color:#0cf;font-weight:bold">.</span> <span style="color:#069;font-weight:bold">import</span> DOMAIN, REQ_LOCK, rcswitch
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>_LOGGER <span style="color:#555">=</span> logging<span style="color:#555">.</span>getLogger(__name__)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>CONF_CODE_OFF <span style="color:#555">=</span> <span style="color:#c30">&#34;code_off&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>CONF_CODE_ON <span style="color:#555">=</span> <span style="color:#c30">&#34;code_on&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>CONF_PROTOCOL <span style="color:#555">=</span> <span style="color:#c30">&#34;protocol&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>CONF_LENGTH <span style="color:#555">=</span> <span style="color:#c30">&#34;length&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>CONF_SIGNAL_REPETITIONS <span style="color:#555">=</span> <span style="color:#c30">&#34;signal_repetitions&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>CONF_ENABLE_RECEIVE <span style="color:#555">=</span> <span style="color:#c30">&#34;enable_receive&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>SWITCH_SCHEMA <span style="color:#555">=</span> vol<span style="color:#555">.</span>Schema(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>	{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>		vol<span style="color:#555">.</span>Required(CONF_CODE_OFF): vol<span style="color:#555">.</span>All(cv<span style="color:#555">.</span>ensure_list_csv, [cv<span style="color:#555">.</span>positive_int]),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>		vol<span style="color:#555">.</span>Required(CONF_CODE_ON): vol<span style="color:#555">.</span>All(cv<span style="color:#555">.</span>ensure_list_csv, [cv<span style="color:#555">.</span>positive_int]),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>		vol<span style="color:#555">.</span>Optional(CONF_LENGTH, default<span style="color:#555">=</span><span style="color:#f60">32</span>): cv<span style="color:#555">.</span>positive_int,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>		vol<span style="color:#555">.</span>Optional(CONF_SIGNAL_REPETITIONS, default<span style="color:#555">=</span><span style="color:#f60">15</span>): cv<span style="color:#555">.</span>positive_int,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>		vol<span style="color:#555">.</span>Optional(CONF_PROTOCOL, default<span style="color:#555">=</span><span style="color:#f60">2</span>): cv<span style="color:#555">.</span>positive_int,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>		vol<span style="color:#555">.</span>Optional(CONF_ENABLE_RECEIVE, default<span style="color:#555">=</span><span style="color:#069;font-weight:bold">False</span>): cv<span style="color:#555">.</span>boolean,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>	}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>PLATFORM_SCHEMA <span style="color:#555">=</span> PLATFORM_SCHEMA<span style="color:#555">.</span>extend(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>	{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>		vol<span style="color:#555">.</span>Required(CONF_SWITCHES): vol<span style="color:#555">.</span>Schema({cv<span style="color:#555">.</span>string: SWITCH_SCHEMA}),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>	}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>)
</span></span></code></pre></div><p>The start is very similar to the init file you import everything and define what the configuration will look like. This what my configuration looks like (the two first line are home assistant specific)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#309;font-weight:bold">switch</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#bbb">  </span>- <span style="color:#309;font-weight:bold">platform</span>:<span style="color:#bbb"> </span>open433<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#bbb">    </span><span style="color:#309;font-weight:bold">switches</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#bbb">      </span><span style="color:#309;font-weight:bold">switchA</span>:<span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">code_on</span>:<span style="color:#bbb"> </span><span style="color:#f60">2389577216</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">code_off</span>:<span style="color:#bbb"> </span><span style="color:#f60">2171473408</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">protocol</span>:<span style="color:#bbb"> </span><span style="color:#f60">2</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">length</span>:<span style="color:#bbb"> </span><span style="color:#f60">32</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">signal_repetitions</span>:<span style="color:#bbb"> </span><span style="color:#f60">5</span><span style="color:#bbb">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#bbb">        </span><span style="color:#309;font-weight:bold">enable_receive</span>:<span style="color:#bbb"> </span><span style="color:#069;font-weight:bold">true</span><span style="color:#bbb">
</span></span></span></code></pre></div><p>Then you need to set up the platform</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">setup_platform</span>(hass, config, add_entities, discovery_info<span style="color:#555">=</span><span style="color:#069;font-weight:bold">None</span>):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	rf <span style="color:#555">=</span> hass<span style="color:#555">.</span>data[DOMAIN]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>	switches <span style="color:#555">=</span> config<span style="color:#555">.</span>get(CONF_SWITCHES)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>	devices <span style="color:#555">=</span> []
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>	<span style="color:#069;font-weight:bold">for</span> dev_name, properties <span style="color:#000;font-weight:bold">in</span> switches<span style="color:#555">.</span>items():
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>		devices<span style="color:#555">.</span>append(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>			Open433Switch(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>				properties<span style="color:#555">.</span>get(CONF_NAME, dev_name),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>				rf,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>				properties<span style="color:#555">.</span>get(CONF_PROTOCOL),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>				properties<span style="color:#555">.</span>get(CONF_LENGTH),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>				properties<span style="color:#555">.</span>get(CONF_SIGNAL_REPETITIONS),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>				properties<span style="color:#555">.</span>get(CONF_CODE_ON),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>				properties<span style="color:#555">.</span>get(CONF_CODE_OFF),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>				properties<span style="color:#555">.</span>get(CONF_ENABLE_RECEIVE),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>			)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>		)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>	add_entities(devices)
</span></span></code></pre></div><p>First I retrieve the rf instance that was stored earlier in the init, get the array of switches in the configuration iterate over them and then declare each device (the Open433Switch class) and tell home assistant about my entities. So about he Open433Switch:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">class</span> <span style="color:#0a8;font-weight:bold">Open433Switch</span>(SwitchEntity):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#069;font-weight:bold">def</span> __init__(self, name, rf, protocol, length, signal_repetitions, code_on, code_off,enable_rx):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>		self<span style="color:#555">.</span>_name <span style="color:#555">=</span> name
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>		self<span style="color:#555">.</span>_state <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>		self<span style="color:#555">.</span>_rf <span style="color:#555">=</span> rf
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>		self<span style="color:#555">.</span>_protocol <span style="color:#555">=</span> protocol
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>		self<span style="color:#555">.</span>_length <span style="color:#555">=</span> length
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>		self<span style="color:#555">.</span>_code_on <span style="color:#555">=</span> code_on
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>		self<span style="color:#555">.</span>_code_off <span style="color:#555">=</span> code_off
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>		self<span style="color:#555">.</span>_signal_repetitions <span style="color:#555">=</span> signal_repetitions
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>		<span style="color:#069;font-weight:bold">if</span> enable_rx:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>			self<span style="color:#555">.</span>_rf<span style="color:#555">.</span>addIncomingPacketListener(self<span style="color:#555">.</span>_incoming)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_incoming</span>(self, packet):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>		<span style="color:#069;font-weight:bold">if</span> <span style="color:#366">isinstance</span>(packet, rcswitch<span style="color:#555">.</span>packets<span style="color:#555">.</span>ReceivedSignal):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>			<span style="color:#069;font-weight:bold">if</span> packet<span style="color:#555">.</span>length <span style="color:#555">==</span> self<span style="color:#555">.</span>_length <span style="color:#000;font-weight:bold">and</span> packet<span style="color:#555">.</span>protocol <span style="color:#555">==</span> self<span style="color:#555">.</span>_protocol:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>				<span style="color:#069;font-weight:bold">if</span> packet<span style="color:#555">.</span>decimal <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>_code_on:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>					self<span style="color:#555">.</span>_state <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>					self<span style="color:#555">.</span>schedule_update_ha_state()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>				<span style="color:#069;font-weight:bold">if</span> packet<span style="color:#555">.</span>decimal <span style="color:#000;font-weight:bold">in</span> self<span style="color:#555">.</span>_code_off:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>					self<span style="color:#555">.</span>_state <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>					self<span style="color:#555">.</span>schedule_update_ha_state()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>	<span style="color:#99f">@property</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">should_poll</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>		<span style="color:#069;font-weight:bold">return</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>	<span style="color:#99f">@property</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">name</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>		<span style="color:#069;font-weight:bold">return</span> self<span style="color:#555">.</span>_name
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>	<span style="color:#99f">@property</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">is_on</span>(self):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>		<span style="color:#069;font-weight:bold">return</span> self<span style="color:#555">.</span>_state
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">_send_code</span>(self, code_list, protocol, length):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>		<span style="color:#069;font-weight:bold">with</span> REQ_LOCK:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>			self<span style="color:#555">.</span>_rf<span style="color:#555">.</span>setRepeatTransmit(self<span style="color:#555">.</span>_signal_repetitions)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>			<span style="color:#069;font-weight:bold">for</span> code <span style="color:#000;font-weight:bold">in</span> code_list:
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>				packet <span style="color:#555">=</span> rcswitch<span style="color:#555">.</span>packets<span style="color:#555">.</span>SendDecimal(value<span style="color:#555">=</span>code, length<span style="color:#555">=</span>length, protocol<span style="color:#555">=</span>protocol, delay<span style="color:#555">=</span><span style="color:#f60">700</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>				self<span style="color:#555">.</span>_rf<span style="color:#555">.</span>send(packet)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>		<span style="color:#069;font-weight:bold">return</span> <span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">turn_on</span>(self, <span style="color:#555">**</span>kwargs):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>		<span style="color:#069;font-weight:bold">if</span> self<span style="color:#555">.</span>_send_code(self<span style="color:#555">.</span>_code_on, self<span style="color:#555">.</span>_protocol, self<span style="color:#555">.</span>_length):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>			self<span style="color:#555">.</span>_state <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">True</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>			self<span style="color:#555">.</span>schedule_update_ha_state()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>	<span style="color:#069;font-weight:bold">def</span> <span style="color:#c0f">turn_off</span>(self, <span style="color:#555">**</span>kwargs):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>		<span style="color:#069;font-weight:bold">if</span> self<span style="color:#555">.</span>_send_code(self<span style="color:#555">.</span>_code_off, self<span style="color:#555">.</span>_protocol, self<span style="color:#555">.</span>_length):
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>			self<span style="color:#555">.</span>_state <span style="color:#555">=</span> <span style="color:#069;font-weight:bold">False</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>			self<span style="color:#555">.</span>schedule_update_ha_state()
</span></span></code></pre></div><p>Basically in the class the thing you need to worry about is the Open433Switch() that is expanding the SwitchEntity class, the should_poll propriety, the name propriety, the is_on propriety, the turn_on function and the turn_off function.</p>
<p>These function/ proprieties executes / get polled at different time. Example when you change the status in the dashboard the turn_on / turn_off function get executed. When the code executes self.schedule_update_ha_state() the proprieties get polled</p>
<p>And that's the basics</p>
<h2 id="links">
    Links 
    
    <a class="header-link" href="#links">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>

    
        

        
            








    


<blockquote class="embed embed-developers_home_assistant_io">
    
        
            <a target="_blank" href="https://developers.home-assistant.io/docs/creating_integration_file_structure" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/7bcdc297bf64c7e7223268ad9c59ae96_hu888cfd3b1c719323750ccd2a89afd871_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/7bcdc297bf64c7e7223268ad9c59ae96_hu888cfd3b1c719323750ccd2a89afd871_0_0x720_resize_q90_h2_box_3.webp' alt=''>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://developers.home-assistant.io/docs/creating_integration_file_structure">Integration file structure | Home Assistant Developer Docs</a>
        
        
            <p>Each integration is stored inside a directory named after the integration domain. The domain is a short name consisting of characters and underscores. This domain has to be unique and cannot be changed. Example of the domain for the mobile app integration: mobileapp. So all files for this integration are in the folder mobileapp/.</p>
        
    </div>
</blockquote>

 ]]></content:encoded></item><item><title>Hacking the hardware store fan</title><description> &lt;p>A while ago someone bought me a little table fan. In summer that's the best thing ever however it lacks a main requirement to be on my desk: It doesn't have any kind of connectivity.&lt;/p></description><link>https://blog.thestaticturtle.fr/hacking-the-hardware-store-fan/</link><guid>https://blog.thestaticturtle.fr/hacking-the-hardware-store-fan/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Sun, 28 Jun 2020 21:41:11 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/hacking-the-hardware-store-fan/images/cover_hu021e11627ff8a6b329f3b01ea454c7d3_86646_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/hacking-the-hardware-store-fan/images/cover_hu021e11627ff8a6b329f3b01ea454c7d3_86646_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Hacking the hardware store fan</h1>
<span class="subtitle"><p>A while ago someone bought me a little table fan. In summer that's the best thing ever however it lacks a main requirement to be on my desk: It doesn't have any kind of connectivity.</p></span>
<br>

    <img class="" src='/hacking-the-hardware-store-fan/images/cover_hu021e11627ff8a6b329f3b01ea454c7d3_86646_1350x900_fit_q80_box.jpeg' alt="Hacking the hardware store fan"/>

<hr>

<p>After a bit of deliberation in my head I was fixed on adding an esp8266 and tho Wi-Fi capabilities. It was the best option to have ota updates and some functionalities and enough space to fit all that.</p>
<p>So I started by &quot;reverse engineering&quot; the original board, it uses two touch input to turn on/off and control speed, it has 4 speed indicators led and a charged/charging led. The fan motor is running on ~8.5V but tolerate 10V well and probably uses PWM to control speed. Unfortunately as for any Chinese product all the ICs are blank/scratched. The fan can run on one Li-ion battery (Probably a cheap ass 18650)

    <figure>
        <a target="_blank" href="images/dl_IMG_20200622_133824__01__02-1.jpg" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_IMG_20200622_133824__01__02-1_hu9cae8cf51ba1737e60f181353f1cb8c0_273074_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="the-making">
    The making 
    
    <a class="header-link" href="#the-making">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="schematic">
    Schematic 
    
    <a class="header-link" href="#schematic">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>So I fired up Easy EDA and created this schematic, I basically ripped of Great Scott charge boost converter circuit added a second boost converter to get 10V and 3.3V (After adjusting the values of course). I used the same touch sensor that I used in my TurtleAuth project (The TTP233D) and used the premade ESP-12F module, all the IO just fit on the esp module (With an overlapping for IO2)

    <figure>
        <a target="_blank" href="images/dl_Schematic_esp8266-ed_fan_2020-06-22_13-50-23.png" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_Schematic_esp8266-ed_fan_2020-06-22_13-50-23_hu7ad14bee185e9fc9e36021b1190ff36a_79668_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h3 id="pcb">
    PCB 
    
    <a class="header-link" href="#pcb">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The PCB design was pretty straight forward but the main issue was recreating the board size from the original manufacturer

    <figure>
        <a target="_blank" href="images/dl_image.jpeg" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_image_hu3db2054d1b93bded30b3b49ad4326198_471319_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


At the end, the two screw hole ended up to be somewhat in the right place and I needed to cut a bit of the PCB to make some place for the posts. In also needed to flip the LEDs 180 to align them with the light pipes.

    <figure>
        <a target="_blank" href="images/dl_image-2.jpeg" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_image-2.jpeg" />
        </a>
        
    </figure>


The schematic called for a 20pF capacitor for the touch sensing, however after placing the pcb into the case the sensitivity was way too low it wasn't getting detected at all, so I tried a ~3pF cap and it somewhat worked but what worked the best was just no caps at all.</p>
<p>I also did a design mistake by putting a 3.3v boost converter on a 4v power supply, (ironic heh) so I desoldered the components necessaries to this rail and bodge an AMS1117-3.3 and it works really well.</p>
<p>The protection IC was a nightmare, there was no orientation marker that I could see (both for the mosfet and the ic). What threw me off a lot is that by just plugging the battery the protection circuit doesn't allow the current to pass, it turns out that you need to plug the charger after plugging the battery to trigger the ic and kick off the power supplys.</p>
<p>Another design &quot;mistake&quot; is that I forgot to tie the enable line to the input line of the boost converter IC. Thanks to the IC designer that put the pins next to each others and I just soldered the two pins together.</p>
<p>The final problem that I encountered is that I choose a mosfet that trigger at a voltage too high (stupid me that didn't read the data sheet) so after digging a lot in my part tray I found an old IRFZ44N which is bigger than the one originally choose so you can see it a bit (there is a 1 mm gap that means the enclosure doesn't close properly), but this mosfet works great now</p>
<p>At the end of two days of debugging / re-soldering / bodging, the assembled pcb looked like this:

    <figure>
        <a target="_blank" href="images/dl_image-1.jpeg" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_image-1_hu574cd5ac3c21f7cc4258d47b301d8ec1_325108_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="the-software">
    The software 
    
    <a class="header-link" href="#the-software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<h3 id="arduino-code">
    Arduino code 
    
    <a class="header-link" href="#arduino-code">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>I tried to reproduce the original behavior has much a possible (One button increment the speed and on turn on and off the fan) and it works pretty great</p>
<p>The esp8266 connects to my Wi-Fi and my mqtt server for the home assistant integration. It also has OTA update functionality because it's just annoying to unscrew everything just to update something (and it's cool to say &quot;BRB Just have to update my fan&quot;)</p>
<p>Code (And schematic+pcb without correction) on github:</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/Esp8266ThisFan" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/b403e29d27d472e8779f7b57e367fbd9_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/b403e29d27d472e8779f7b57e367fbd9_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Contribute to TheStaticTurtle/Esp8266ThisFan development by creating an account on GitHub.'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/Esp8266ThisFan">GitHub - TheStaticTurtle/Esp8266ThisFan</a>
        
        
            <p>Contribute to TheStaticTurtle/Esp8266ThisFan development by creating an account on GitHub.</p>
        
    </div>
</blockquote>
        
    

<h3 id="home-assistant-integration">
    Home assistant integration 
    
    <a class="header-link" href="#home-assistant-integration">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h3>
<p>The home assistant component was done via the fan component using the mqtt integration, unfortunately home assistant only support 3speed (my fan has 4) so I just skipped the max speed and only used the low medium and high which is sufficient.</p>
<p>Thanks to hacs and custom lovelace integration I managed to find a good-looking card for the ui

    <figure>
        <a target="_blank" href="images/dl_image.png" >
            <img alt="" src="/hacking-the-hardware-store-fan/images/dl_image.png" />
        </a>
        
    </figure>


All in all it works pretty great and more a challenge to IoT everything than to actually make something useful out of it.</p>
<p>There is still a few bugs like pressing the change speed button will crash the esp (which is not that bad it replaces the reset button inside the enclosure). I would really like to find a suitable high frequency mosfet to remove the annoying pwm frequency at higher speeds (Plus the boost converter make some noise, but it's inside and only make noise under load so it's ok). Finally, having the 4th speed on home assistant would be nice to have</p>

 ]]></content:encoded></item><item><title>Let's turn lights on with the computer the 433Mhz way</title><description> &lt;p>A while ago I started using home assistant on a pi and bought some inexpensive 433Mhz ac wireless switches. However, after one month of intense use the Raspberry Pi didn't have enough power, so I bought a cheap computer of eBay.&lt;/p></description><link>https://blog.thestaticturtle.fr/lets-turn-lights-on-with-the-computer-the-433mhz-way/</link><guid>https://blog.thestaticturtle.fr/lets-turn-lights-on-with-the-computer-the-433mhz-way/</guid><category> Diy</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 09 Jun 2020 19:58:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/cover_hu98eeca3d4e1a20b161cdf36e76f61a3c_571443_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/cover_hu98eeca3d4e1a20b161cdf36e76f61a3c_571443_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Let&#39;s turn lights on with the computer the 433Mhz way</h1>
<span class="subtitle"><p>A while ago I started using home assistant on a pi and bought some inexpensive 433Mhz ac wireless switches. However, after one month of intense use the Raspberry Pi didn't have enough power, so I bought a cheap computer of eBay.</p></span>
<br>

    <img class="" src='/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/cover_hu98eeca3d4e1a20b161cdf36e76f61a3c_571443_1350x900_fit_q80_box.jpg' alt="Let&#39;s turn lights on with the computer the 433Mhz way"/>

<hr>

<p>It worked well expect that I didn't have control over my switches. Sure I could have use the raspberry in conjunction with the computer but that would be overkill, so I started designing an Arduino on a stick with USB and 433Mhz tx and rx. The first design I made was way too optimistic I planed to use a rf switch to switch between rx and tx and use a single sma port for the antenna but that didn't go so well and neither tx nor rx was working, so I just gave up for a few weeks and then made version two.</p>
<h2 id="hardware">
    Hardware 
    
    <a class="header-link" href="#hardware">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Version two is smaller and have two coiled antennas instead of a sma port and it works well enough. It's still based around an atmega328 running at 16MHz with the Arduino UNO bootloader and a CH340 USB to Serial converter. As it's so versatile in its operation, I also added expansion port for SDA / SCL / A0 / Pin8 (PD0) in addition to the iscp header with the spi bus so that the board could be used as standalone transmitter to send sensor values for example.</p>
<p>So here are some photos of the v2 board:

    <figure>
        <a target="_blank" href="images/dl_chrome_2020-06-16_01-04-59.png" >
            <img alt="" src="/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/dl_chrome_2020-06-16_01-04-59.png" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_chrome_2020-06-16_01-05-11.png" >
            <img alt="" src="/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/dl_chrome_2020-06-16_01-05-11.png" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_IMG_20200616_010949.jpg" >
            <img alt="" src="/lets-turn-lights-on-with-the-computer-the-433mhz-way/images/dl_IMG_20200616_010949_hu98eeca3d4e1a20b161cdf36e76f61a3c_571443_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


Unfortunately as I'm an idiot I put the resistor for the power led on the wrong side of the board, so I couldn't solder it up and corrected it for v2.1</p>
<h2 id="software">
    Software 
    
    <a class="header-link" href="#software">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>As I had good luck with the rcswitch library I wanted to use this library on the Arduino side and wanted to make a python library (for future integration with home assistant) resembling as much as possible as rcswitch. To make the board communicate with the software I was a first thinking a serial communication of the type &quot;SEND:2523794944:32:2:700&quot; but that's way too complicated and I wanted to try struct serialization so that what I did I made 4 structs for configuration / packet sent / packet received / acknowledgment and all prefixed by a char[17] for the packet type (&quot;rcswitch_conf&quot; / &quot;send_decimal&quot; / &quot;receive_signal&quot; / &quot;ack&quot;) and the use this code to extract the raw bytes that I read from the serial port:</p>
<pre><code>char type[17];  //Create a tmp buffer to store the type of packet received
memcpy (&amp;type, &amp;data_buffer, 17); //Copy only the first 17bytes to the tmp buffer (See ptype in each struct in packet.h)
</code></pre>
<p>Basically when the computer sends a command it toggles the rx led turn on the tx one do the action toggle backs the leds and sends an ack packet with sendAck();</p>
<p>On the python side of things I use struct to unpack the data coming from the board example for the Signal received packet:</p>
<pre><code>class ReceivedSignal(object):
	&quot;&quot;&quot;docstring for received_signal_packet_t&quot;&quot;&quot;

	def __init__(self):
		super(packets.ReceivedSignal, self).__init__()
		self.format = &quot;&lt;17sIIHHH&quot;

		self.time = -1
		self.decimal = -1
		self.length = -1
		self.delay = -1
		# self.raw     = None
		self.protocol = 0

	def __str__(self):
		return &quot;&lt;ReceivedSignal time=&quot; + str(self.time) + &quot; decimal=&quot; + str(
			self.decimal) + &quot; length=&quot; + str(
			self.length) + &quot; delay=&quot; + str(self.delay) + &quot; protocol=&quot; + str(self.protocol) + &quot;&gt;&quot;

	def parse(self, raw):
		unpacked = struct.unpack(self.format, bytearray(raw))
		self.time = unpacked[1]
		self.decimal = unpacked[2]
		self.length = unpacked[3]
		self.delay = unpacked[4]
		self.protocol = unpacked[5]
		return self
</code></pre>
<p>The library ins't finished yet but look like this</p>
<pre><code>import rcswitch

mySwitch = rcswitch.RCSwitch(&quot;COM3&quot;)
time.sleep(2)

# for packet in mySwitch.listen():
# 	print(packet)

packet_on  = rcswitch.packets.SendDecimal(value=2523794944, length=32, protocol=2, delay=700)
packet_off = rcswitch.packets.SendDecimal(value=2658012672, length=32, protocol=2, delay=700)
mySwitch.setRepeatTransmit(5)

while True:
	mySwitch.send(packet_on)
	mySwitch.receive_packet(timeout=0.1)
	time.sleep(1)

	mySwitch.send(packet_off)
	mySwitch.receive_packet(timeout=0.1)
	time.sleep(1)
</code></pre>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/Open433" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/216d8edf8bebe687ac318f974407ee04_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/216d8edf8bebe687ac318f974407ee04_hu902c2667d7108f6e939dc978d88af368_0_0x720_resize_q90_h2_box_3.webp' alt='This project is an opensource usb 433Mhz rf transmitter / receiver based on an atmega328p - TheStaticTurtle/Open433'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/Open433">GitHub - TheStaticTurtle/Open433: This project is an opensource usb 433Mhz rf transmitter / receiver based on an atmega328p</a>
        
        
            <p>This project is an opensource usb 433Mhz rf transmitter / receiver based on an atmega328p - TheStaticTurtle/Open433</p>
        
    </div>
</blockquote>

 ]]></content:encoded></item><item><title>Let's make a DIY gpg usb key</title><description> &lt;p>Some of you may know that you can use yubikey's as a gpg smart card for message encryption / ssh login however starting at $45 for a compatible one I didn't really like that, so I searched a bit to make a DIY one.&lt;/p></description><link>https://blog.thestaticturtle.fr/lets-make-a-diy-gpg-usb-key/</link><guid>https://blog.thestaticturtle.fr/lets-make-a-diy-gpg-usb-key/</guid><category> Diy</category><category> Security</category><category> Electronics</category><dc:creator> Samuel</dc:creator><pubDate>Sun, 07 Jun 2020 19:47:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/lets-make-a-diy-gpg-usb-key/images/cover_hu0f24f1e97eb67ff26ce459da87fd6ac5_447017_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/lets-make-a-diy-gpg-usb-key/images/cover_hu0f24f1e97eb67ff26ce459da87fd6ac5_447017_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>Let&#39;s make a DIY gpg usb key</h1>
<span class="subtitle"><p>Some of you may know that you can use yubikey's as a gpg smart card for message encryption / ssh login however starting at $45 for a compatible one I didn't really like that, so I searched a bit to make a DIY one.</p></span>
<br>

    <img class="" src='/lets-make-a-diy-gpg-usb-key/images/cover_hu0f24f1e97eb67ff26ce459da87fd6ac5_447017_1350x900_fit_q80_box.jpg' alt="Let&#39;s make a DIY gpg usb key"/>

<hr>

<p>Turns out, there is a project by <a target="_blank" href="https://blog.danman.eu/2-usb-crypto-token-for-use-with-gpg-and-ssh/">danman</a> creating such token with a St link V2 (the stm32/8 programmer) after looking a bit I found out that there is the gnuk project that implements just that on a stm32f103.</p>
<h2 id="testing">
    Testing 
    
    <a class="header-link" href="#testing">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I, next found that there is another gpg key called the Nitro Key which is cheaper but still a bit pricey. As I already had some Blue pills (stm32f103 devboard) lying around I decided to test it.</p>
<p>I started by finding the source code of gnuk (and the sub module chopstx) which was more difficult than I thought (Some git services were shut down), upon finding the source on GitLab I cloned it on my GitHub (<a target="_blank" href="https://github.com/TheStaticTurtle/gnuk">https://github.com/TheStaticTurtle/gnuk</a><a target="_blank" href="https://github.com/TheStaticTurtle/chopstx/">https://github.com/TheStaticTurtle/chopstx/</a>).</p>
<p>I named my project TurtleAuth, so I started by just using the same config as the ST_DONGLE target since I don't plan on changing the MCU and the officials ones are based on the stm32f103c8 (clones are often the stm32f101 which doesn't officially have &quot;USB capabilities&quot; but can be used anyway somehow). I proceed by creating my own board definition (chopstx/board/turtle-auth.h) and adding / changing configuration like the led pin which I set to PA13 (On board led on the bluepill) and configuring a button on PA8 in pull up</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#09f;font-style:italic">/*
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span><span style="color:#09f;font-style:italic"> * Port A setup.
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#09f;font-style:italic"> * PA11 - Push Pull output 10MHz 0 default (until USB enabled) (USBDM)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#09f;font-style:italic"> * PA12 - Push Pull output 10MHz 0 default (until USB enabled) (USBDP)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#09f;font-style:italic"> *
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#09f;font-style:italic"> * Port C setup.
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#09f;font-style:italic"> * PC13 - Push pull output 50MHz (LED 1:ON 0:OFF)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#09f;font-style:italic"> * ------------------------ Default
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#09f;font-style:italic"> * PAx  - input with pull-up
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#09f;font-style:italic"> * PCx  - input with pull-up
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#09f;font-style:italic"> */</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#099">#define VAL_GPIO_USB_ODR            0xFFFFE6FF
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#099">#define VAL_GPIO_USB_CRL            0x88888888      </span><span style="color:#09f;font-style:italic">/*  PA7...PA0 */</span><span style="color:#099">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#099">#define VAL_GPIO_USB_CRH            0x88811888      </span><span style="color:#09f;font-style:italic">/* PA15...PA8 */</span><span style="color:#099">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#099"></span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#099">#define VAL_GPIO_OTHER_ODR          VAL_GPIO_USB_ODR
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#099">#define VAL_GPIO_OTHER_CRL          VAL_GPIO_USB_CRL
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#099">#define VAL_GPIO_OTHER_CRH          VAL_GPIO_USB_CRH
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#099"></span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#099">#define VAL_GPIO_LED_ODR            0xFFFFFFFF
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#099">#define VAL_GPIO_LED_CRL            0x88888888      </span><span style="color:#09f;font-style:italic">/*  PC7...PC0 */</span><span style="color:#099">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#099">#define VAL_GPIO_LED_CRH            0x88388888      </span><span style="color:#09f;font-style:italic">/* PC15...PC8 */</span><span style="color:#099">
</span></span></span></code></pre></div><p>The button will still not work with this code I still had to add it the gnuk / chopstx source code. First I need to add a new function in chopstx to wait until the button has been pressed. I set it up to blink the led every 100ms and break out of the loop if the button goes high (I have a 10k pull down resistor on the button)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">void</span> <span style="color:#c0f">wait_button</span>() {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#099">#if defined(GPIO_BUTTON_PIN)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#099"></span>        <span style="color:#069;font-weight:bold">while</span> (<span style="color:#f60">1</span>){
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>            <span style="color:#c0f">set_led</span>(<span style="color:#f60">1</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>            <span style="color:#c0f">wait</span>(<span style="color:#f60">1000000</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>            <span style="color:#069;font-weight:bold">if</span>( (GPIO_OTHER<span style="color:#555">-&gt;</span>IDR <span style="color:#555">&amp;</span> (<span style="color:#f60">1</span> <span style="color:#555">&lt;&lt;</span> GPIO_BUTTON_PIN)) ) <span style="color:#069;font-weight:bold">break</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>    
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>            <span style="color:#c0f">set_led</span>(<span style="color:#f60">0</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>            <span style="color:#c0f">wait</span>(<span style="color:#f60">1000000</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>            <span style="color:#069;font-weight:bold">if</span>( (GPIO_OTHER<span style="color:#555">-&gt;</span>IDR <span style="color:#555">&amp;</span> (<span style="color:#f60">1</span> <span style="color:#555">&lt;&lt;</span> GPIO_BUTTON_PIN)) ) <span style="color:#069;font-weight:bold">break</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>    <span style="color:#099">#endif
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#099"></span>}
</span></span></code></pre></div><p>Next I added the wait button function in the openpgp.c file at line 936 and 1144</p>
<pre tabindex="0"><code>#ifdef CONFIRM_BUTTON_SUPPORT
  wait_button();
#endif
</code></pre><p>(<a target="_blank" href="https://github.com/TheStaticTurtle/gnuk/search?q=wait_button&amp;amp;unscoped_q=wait_button">https://github.com/TheStaticTurtle/gnuk/search?q=wait_button&amp;unscoped_q=wait_button</a>)</p>
<p>and added the CONFIRM_BUTTON_SUPPORT option to the command line arguments (<a target="_blank" href="https://github.com/TheStaticTurtle/gnuk/search?q=CONFIRM_BUTTON_SUPPORT&amp;amp;unscoped_q=CONFIRM_BUTTON_SUPPORT">https://github.com/TheStaticTurtle/gnuk/search?q=CONFIRM_BUTTON_SUPPORT&amp;unscoped_q=CONFIRM_BUTTON_SUPPORT</a>)</p>
<p>Since I didn't have any experience programming and stm32 outside the Arduino environment I followed danman guide to build and transfer the program.</p>
<p>I started by creating a config for OpenOCD (Debugger / Programmer) and setting up to use a st link V2 to flash the software.</p>
<pre><code>#daemon configuration
telnet_port 4444
gdb_port 3333

#interface
interface hla
hla_layout stlink
hla_device_desc &quot;ST-LINK/V2&quot;
hla_vid_pid 0x0483 0x3748

#transport select swd

# The chip has 64KB sram
set WORKAREASIZE 0x10000

source [find target/stm32f1x.cfg]
#adapter_khz 100
gdb_breakpoint_override hard
</code></pre>
<p>OpenOCD open a telnet server on port 4444 that you can use to send commands to it. I, next wrote a build and flash script to simplify my life during testing, since I change the source code on a different computer that the one I used to flash (Windows/Linux), I added git pull to make sure that I'm up to date with my GitHub</p>
<p>Build script:</p>
<pre><code>cd gnuk
git pull
git submodule update --init
git submodule sync
git submodule update --remote

cd chopstx
git pull origin master
cd ..

cd src
./configure --vidpid=234b:0000 --target=TURTLE_AUTH --enable-confirm-button
make clean
make
</code></pre>
<p>Flash script (Not the best but it works):</p>
<pre><code>echo 'stm32f1x unlock 0' &gt; tmp.network
echo 'reset halt' &gt;&gt; tmp.network
echo 'stm32f1x unlock 0' &gt;&gt; tmp.network
echo 'reset halt' &gt;&gt; tmp.network
echo 'flash erase_sector 0 0 127' &gt;&gt; tmp.network
echo 'flash write_bank 0 ./gnuk/src/build/gnuk.bin 0' &gt;&gt; tmp.network
echo 'reset' &gt;&gt; tmp.network
echo 'exit' &gt;&gt; tmp.network

cat tmp.network | netcat 127.0.0.1 4444
rm tmp.network
</code></pre>
<p>So after flashing sucess the device show up in dmseg</p>
<pre><code>[  808.561960] usb 2-1: Product: Gnuk Token
[  808.561964] usb 2-1: Manufacturer: Free Software Initiative of Japan
[  808.561967] usb 2-1: SerialNumber: TURTLE-1.2.15-87033357
</code></pre>
<p>After executing gpg --card-status I was greeted by a wonderful output that told me that everything was working correctly</p>
<pre><code>$ gpg --card-status

Reader ...........: 234B:0000:TURTLE-1.2.15-87033357:0
Application ID ...: D276000124010200FFFE870333570000
Version ..........: 2.0
Manufacturer .....: unmanaged S/N range
Serial number ....: 87033357
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]
</code></pre>
<p>After that I could use it like a normal gpg smart card. However, this mess isn't superb to carry around and very fragile:

    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_020442.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_020442_hu74d397aa7a96e171938d45347e6d855a_405904_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>
<h2 id="making-it-cooler">
    Making it cooler 
    
    <a class="header-link" href="#making-it-cooler">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>So I decided to improve my by design and soldering skill by only choosing 0603 sized components (Which is retrospect a size that I won't go further unless I get a magnifying glass or a microscope).</p>
<p>I wanted to have to boards one on top of each other to clean up the design and make it appear less &quot;hacky&quot; and I recently discovered the TTP223E Touch control ic, so I don't wanted to use a typical pushbutton to validate the access. So I basically cloned the blue pill design and added the ic:

    <figure>
        <a target="_blank" href="images/dl_Schematic_Stm32GPG_2020-06-08_03-07-59.png" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_Schematic_Stm32GPG_2020-06-08_03-07-59_hu34dd070e6fd23c131ab154ce0fd56975_375127_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>


I replaced the BOOT0/1 header with tiny pads that you solder to change the position, the USB connector to a USB A male one , I also tried to solder a metal dome for the reset button but that was a TOTAL failure of course I chose LCSC as my component provider (Partly for the shipping discount when you order via JLCPCB) and here is the <a target="_blank" href="https://data.thestaticturtle.fr/ShareX/BOM_Stm32GPG_2020-06-08_03-15-35.csv">BOM list</a> the PCB design was by far the most challenging one that I did since I tried to do my best to reduce the board size (which cause me some problem during assembly later) at the end I submitted a panelized version of this board:

    <figure>
        <a target="_blank" href="images/dl_chrome_2020-06-08_03-19-01.png" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_chrome_2020-06-08_03-19-01.png" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_chrome_2020-06-08_03-20-07.png" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_chrome_2020-06-08_03-20-07.png" />
        </a>
        
    </figure>


The bottom and top board are connected to allow me to test everything without the top board being in the way. After finally receiving my PCB from JLCPCB I snapped on board out and started soldering

    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_033253.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_033253_hu8b4e088acc27c580ad046ff3ca892bb0_345466_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_033300.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_033300_hu54a9805a863c3924f1435def69454238_537625_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


And you can probably see the big mistake that I made, the USB is too far in and interferes with the board (I should really get the real 3d Model) a bit of surgery fixes that. As it turns out gunk doesn't need the 32.768kHz oscillator, so I didn't solder it but left the pads is I ever want to repurpose the board in the future. After a painful soldering session this was the result and I'm very happy with it:

    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_033954-1.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_033954-1_hu0f24f1e97eb67ff26ce459da87fd6ac5_447017_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_034010.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_034010_hu77da4e3d4227a734ef7e07e1d74eb6b1_382817_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


    <figure>
        <a target="_blank" href="images/dl_IMG_20200608_034146.jpg" >
            <img alt="" src="/lets-make-a-diy-gpg-usb-key/images/dl_IMG_20200608_034146_hu5a6595f28ed958efcd984f5549a85275_187884_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>


Links</p>
<ul>
<li><a target="_blank" href="https://github.com/TheStaticTurtle/chopstx/">https://github.com/TheStaticTurtle/chopstx/</a></li>
<li><a target="_blank" href="https://github.com/TheStaticTurtle/gnuk/">https://github.com/TheStaticTurtle/gnuk/</a></li>
<li><a target="_blank" href="https://blog.danman.eu/2-usb-crypto-token-for-use-with-gpg-and-ssh/">https://blog.danman.eu/2-usb-crypto-token-for-use-with-gpg-and-ssh/</a></li>
<li><a target="_blank" href="https://www.fsij.org/doc-gnuk/">https://www.fsij.org/doc-gnuk/</a></li>
<li><a target="_blank" href="https://salsa.debian.org/gnuk-team/gnuk/gnuk">https://salsa.debian.org/gnuk-team/gnuk/gnuk</a></li>
<li><a target="_blank" href="https://salsa.debian.org/gnuk-team/chopstx/chopstx">https://salsa.debian.org/gnuk-team/chopstx/chopstx</a></li>
</ul>

 ]]></content:encoded></item><item><title>Experimenting with the OpenGraph protocol</title><description> &lt;p>Some time ago I remembered about the opengraph protocol&lt;/p></description><link>https://blog.thestaticturtle.fr/experimenting-with-the-opengraph-protocol/</link><guid>https://blog.thestaticturtle.fr/experimenting-with-the-opengraph-protocol/</guid><category> Research</category><category> Security</category><category> Web</category><dc:creator> Samuel</dc:creator><pubDate>Sun, 10 May 2020 19:58:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/experimenting-with-the-opengraph-protocol/images/cover_hu0d33f158e88d3f31e141cbf62a231de6_33750_0x720_resize_q90_h2_box_3.webp"/><media:content url="https://blog.thestaticturtle.fr/experimenting-with-the-opengraph-protocol/images/cover_hu0d33f158e88d3f31e141cbf62a231de6_33750_0x720_resize_q90_h2_box_3.webp" medium="image"/><content:encoded><![CDATA[ <h1>Experimenting with the OpenGraph protocol</h1>
<span class="subtitle"><p>Some time ago I remembered about the opengraph protocol</p></span>
<br>

    <img class="" src='/experimenting-with-the-opengraph-protocol/images/cover_hu0d33f158e88d3f31e141cbf62a231de6_33750_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Experimenting with the OpenGraph protocol"/>

<hr>

<blockquote>
<p>The <a target="_blank" href="http://ogp.me/">Open Graph protocol</a> enables any web page to become a rich object in a social graph. For instance, this is used on Facebook to allow any web page to have the same functionality as any other object on Facebook. While many different technologies and schemas exist and could be combined together, there isn't a single technology which provides enough information to richly represent any web page within the social graph. The Open Graph protocol builds on these existing technologies and gives developers one thing to implement. Developer simplicity is a key goal of the Open Graph protocol which has informed many of <a target="_blank" href="http://www.scribd.com/doc/30715288/The-Open-Graph-Protocol-Design-Decisions">the technical design decisions</a>.</p>
</blockquote>
<p>However, this can be easily exploited to create phishing attacks. For instance, imagine that a friend ask you about a wikipedia article and for some reason you want him to go to your website (steal login for example).</p>
<p>I'm going to put screenshots of discord because it react very well with thi attack.</p>
<p>Imagine a simple html page:</p>
<pre><code>&lt;html&gt;
 &lt;head&gt;
   &lt;title&gt;Title&lt;/title&gt;
 &lt;/head&gt;
 &lt;body&gt;
   &lt;center&gt;&lt;h1&gt;YOLO&lt;/h1&gt;&lt;/center&gt;
 &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Will give you a message like this, not very clickbaity

    <figure>
        <a target="_blank" href="images/dl_xnxKw33.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_xnxKw33.png" />
        </a>
        
    </figure>


Now lets add the OpenGraph protocol (See <a target="_blank" href="https://ogp.me/#metadata">doc</a>)</p>
<pre><code>&lt;html prefix=&quot;og: http://ogp.me/ns/video#&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;LOL&lt;/title&gt;
        &lt;meta property=&quot;og:site_name&quot; content=&quot;Wikipedia&quot;&gt;
        &lt;meta property=&quot;og:type&quot; content=&quot;website&quot; /&gt;   
        &lt;meta property=&quot;og:title&quot; content=&quot;The Flash (2014 TV series)&quot; /&gt;  
        &lt;meta property=&quot;og:description&quot; content=&quot;The Flash is an American superhero television series developed by Greg Berlanti, Andrew Kreisberg, and Geoff Johns, airing on The CW. It is based on the DC Comics character Barry Allen / Flash, a costumed superhero crime-fighter with the power to move at superhuman speeds. It is a spin-off from Arrow, existing in the same fictional universe known as Arrowverse. The series follows Barry Allen, portrayed by Grant Gustin, a crime scene investigator who gains super-human speed, which he uses to fight criminals, including others who have also gained superhuman abilities.&quot; /&gt;  
        &lt;meta property=&quot;og:image&quot; content=&quot;------------/-----/writeup/image.jpg&quot; /&gt;   
        &lt;meta property=&quot;og:url&quot; content=&quot;https://en.wikipedia.org/wiki/The_Flash_(2014_TV_series)&quot; /&gt;
    &lt;/head&gt;
    &lt;body&gt;  
        &lt;center&gt;
            &lt;h1&gt;YOLO&lt;/h1&gt;
        &lt;/center&gt; 
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>This very simple code will give you this:

    <figure>
        <a target="_blank" href="images/dl_zLkyXEM.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_zLkyXEM.png" />
        </a>
        
    </figure>


Much better someone will be more likely to click the link however the real article dosen't look like this and maybe your friend is a book head and will spot the difference, the real one looks like this:

    <figure>
        <a target="_blank" href="images/dl_vYMgakC.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_vYMgakC.png" />
        </a>
        
    </figure>


So how can we solve this? As it turn's out, you can tell opengraph the size of the image:</p>
<ul>
<li><code>og:image:width</code> - The number of pixels wide.</li>
<li><code>og:image:height</code> - The number of pixels high.</li>
</ul>
<p>so lets try that here's the code so far:</p>
<pre><code>&lt;html prefix=&quot;og: http://ogp.me/ns/video#&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;LOL&lt;/title&gt;
        &lt;meta property=&quot;og:site_name&quot; content=&quot;Wikipedia&quot;&gt;
        &lt;meta property=&quot;og:type&quot; content=&quot;website&quot; /&gt;
        &lt;meta property=&quot;og:title&quot; content=&quot;The Flash (2014 TV series)&quot; /&gt; 
        &lt;meta property=&quot;og:description&quot; content=&quot;The Flash is an American superhero television series developed by Greg Berlanti, Andrew Kreisberg, and Geoff Johns, airing on The CW. It is based on the DC Comics character Barry Allen / Flash, a costumed superhero crime-fighter with the power to move at superhuman speeds. It is a spin-off from Arrow, existing in the same fictional universe known as Arrowverse. The series follows Barry Allen, portrayed by Grant Gustin, a crime scene investigator who gains super-human speed, which he uses to fight criminals, including others who have also gained superhuman abilities.&quot; /&gt;   
        &lt;meta property=&quot;og:image&quot; content=&quot;------------/-----/writeup/image.jpg&quot; /&gt;  
        &lt;meta content=&quot;400&quot; property=&quot;og:image:width&quot;&gt;  
        &lt;meta content=&quot;225&quot; property=&quot;og:image:height&quot;&gt;  
        &lt;meta property=&quot;og:url&quot; content=&quot;https://en.wikipedia.org/wiki/The_Flash_(2014_TV_series)&quot; /&gt;
    &lt;/head&gt; 
    &lt;body&gt;   
        &lt;center&gt;
            &lt;h1&gt;YOLO&lt;/h1&gt;
        &lt;/center&gt; 
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>
    <figure>
        <a target="_blank" href="images/dl_oIWDHdB.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_oIWDHdB.png" />
        </a>
        
    </figure>


Well that didn't change much. As it turn's out Twitter has it's own version of opengraph called Cards and discord follow both converting to Twitter opengraph is simple just change <code>og:</code> to <code>twitter:</code><strong>.</strong> You will also need to say that's it's actually a card with the <code>card</code> attribute. So lets test this:</p>
<pre><code>&lt;html prefix=&quot;og: http://ogp.me/ns/video#&quot;&gt;
    &lt;head&gt;  
        &lt;title&gt;LOL&lt;/title&gt;  
        &lt;meta property=&quot;og:site_name&quot; content=&quot;Wikipedia&quot;&gt;
        &lt;meta property=&quot;og:type&quot; content=&quot;website&quot; /&gt; 
        &lt;meta content=&quot;summary_large_image&quot; name=&quot;twitter:card&quot;&gt; 
        &lt;meta property=&quot;og:title&quot; content=&quot;The Flash (2014 TV series)&quot; /&gt;
        &lt;meta name=&quot;twitter:title&quot; content=&quot;The Flash (2014 TV series)&quot; &gt;  
        &lt;meta property=&quot;og:description&quot; content=&quot;The Flash is an American superhero television series developed by Greg Berlanti, Andrew Kreisberg, and Geoff Johns, airing on The CW. It is based on the DC Comics character Barry Allen / Flash, a costumed superhero crime-fighter with the power to move at superhuman speeds. It is a spin-off from Arrow, existing in the same fictional universe known as Arrowverse. The series follows Barry Allen, portrayed by Grant Gustin, a crime scene investigator who gains super-human speed, which he uses to fight criminals, including others who have also gained superhuman abilities.&quot; /&gt;  
        &lt;meta name=&quot;twitter:description&quot; content=&quot;The Flash is an American superhero television series developed by Greg Berlanti, Andrew Kreisberg, and Geoff Johns, airing on The CW. It is based on the DC Comics character Barry Allen / Flash, a costumed superhero crime-fighter with the power to move at superhuman speeds. It is a spin-off from Arrow, existing in the same fictional universe known as Arrowverse. The series follows Barry Allen, portrayed by Grant Gustin, a crime scene investigator who gains super-human speed, which he uses to fight criminals, including others who have also gained superhuman abilities.&quot; &gt;  
        &lt;meta property=&quot;og:image&quot; content=&quot;------------/-----/writeup/image.jpg&quot; /&gt;  
        &lt;meta name=&quot;twitter:image&quot; content=&quot;------------/-----/writeup/image.jpg&quot; &gt;   
        &lt;meta content=&quot;400&quot; property=&quot;og:image:width&quot;&gt; &lt;meta content=&quot;225&quot; property=&quot;og:image:height&quot;&gt;  
        &lt;meta property=&quot;og:url&quot; content=&quot;https://en.wikipedia.org/wiki/The_Flash_(2014_TV_series)&quot; /&gt;   
        &lt;meta name=&quot;twitter:url&quot; content=&quot;https://en.wikipedia.org/wiki/The_Flash_(2014_TV_series)&quot; &gt; 
    &lt;/head&gt; 
    &lt;body&gt;  
        &lt;center&gt;
            &lt;h1&gt;YOLO&lt;/h1&gt;
        &lt;/center&gt; 
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>
    <figure>
        <a target="_blank" href="images/dl_LFU4mr9.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_LFU4mr9.png" />
        </a>
        
    </figure>


<strong>Bingo.</strong></p>
<p>The message now resemble exactly the original. Now if you don't have a domain like en.wikipedia.org.example.com/wiki/The_Flash_(2014_TV_series) to put your page and sent it to your friend just use a url shorter like bitly and it will work fine:

    <figure>
        <a target="_blank" href="images/dl_1oYzt0O.png" >
            <img alt="" src="/experimenting-with-the-opengraph-protocol/images/dl_1oYzt0O.png" />
        </a>
        
    </figure>


This should obviously not be used for phishing but makes a very good entry point</p>
<p>Resources:</p>
<ul>
<li><a target="_blank" href="https://ogp.me/">https://ogp.me/</a></li>
<li><a target="_blank" href="http://developers.facebook.com/tools/debug/">http://developers.facebook.com/tools/debug/</a></li>
</ul>

 ]]></content:encoded></item><item><title>Terminal TV - Watching videos on the terminal</title><description> &lt;p>Playing video in the terminal using ANSI codes&lt;/p></description><link>https://blog.thestaticturtle.fr/terminal-tv-watching-videos-on-the-terminal/</link><guid>https://blog.thestaticturtle.fr/terminal-tv-watching-videos-on-the-terminal/</guid><dc:creator> Samuel</dc:creator><pubDate>Mon, 10 Feb 2020 20:11:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/terminal-tv-watching-videos-on-the-terminal/images/cover.png"/><media:content url="https://blog.thestaticturtle.fr/terminal-tv-watching-videos-on-the-terminal/images/cover.png" medium="image"/><content:encoded><![CDATA[ <h1>Terminal TV - Watching videos on the terminal</h1>
<span class="subtitle"><p>Playing video in the terminal using ANSI codes</p></span>
<br>

    <img class="" src='/terminal-tv-watching-videos-on-the-terminal/images/cover_hu350f989500a0d20366b1251b281d2d25_73887_1350x900_fit_q80_bgffffff_box_3.jpg' alt="Terminal TV - Watching videos on the terminal"/>

<hr>

<h2 id="intro">
    Intro 
    
    <a class="header-link" href="#intro">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I'm currently in college, and we all are using Linux computers with ssh enabled. There is a miss configuration issue that allows me to write into the terminal of other peoples (Not reading it interestingly). We also have crappy system course that no one perticularly likes. And you probably see where I'm going with this.</p>
<h2 id="how">
    How 
    
    <a class="header-link" href="#how">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I first started by making a python prototype of the things but it was terribly slow a 1080p video ran at around 5fps with a terminal size of around 49x189. So I decided time to make a &quot;useful&quot; c++ program.</p>
<p>I choose to use OpenCV for the vast amount of input sources that I take with still having a lot of image processing already in place.</p>
<p>IoCtl is used to get the terminal size, then I use opencv to resize it to the terminal size, then I get the individual rgb colors. Then I use ansi escape code to set the background color of a space.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">void</span> <span style="color:#c0f">draw_frame</span>(Mat frame,<span style="color:#078;font-weight:bold">int</span> termWitdh,<span style="color:#078;font-weight:bold">int</span> termHeight) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>	<span style="color:#078;font-weight:bold">int</span> channels <span style="color:#555">=</span> frame.channels();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>	<span style="color:#078;font-weight:bold">uint8_t</span><span style="color:#555">*</span> pixelPtr <span style="color:#555">=</span> (<span style="color:#078;font-weight:bold">uint8_t</span><span style="color:#555">*</span>)frame.data;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>	<span style="color:#069;font-weight:bold">for</span> (<span style="color:#078;font-weight:bold">int</span> y <span style="color:#555">=</span> <span style="color:#f60">0</span>; y <span style="color:#555">&lt;</span> termHeight<span style="color:#555">-</span><span style="color:#f60">2</span>; y<span style="color:#555">++</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>		<span style="color:#069;font-weight:bold">for</span> (<span style="color:#078;font-weight:bold">int</span> x <span style="color:#555">=</span> <span style="color:#f60">0</span>; x <span style="color:#555">&lt;</span> termWitdh; x<span style="color:#555">++</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>			<span style="color:#078;font-weight:bold">int</span> b <span style="color:#555">=</span> pixelPtr[x<span style="color:#555">*</span>frame.cols<span style="color:#555">*</span>channels <span style="color:#555">+</span> y<span style="color:#555">*</span>channels <span style="color:#555">+</span> <span style="color:#f60">0</span>]; <span style="color:#09f;font-style:italic">// B
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#09f;font-style:italic"></span>			<span style="color:#078;font-weight:bold">int</span> g <span style="color:#555">=</span> pixelPtr[x<span style="color:#555">*</span>frame.cols<span style="color:#555">*</span>channels <span style="color:#555">+</span> y<span style="color:#555">*</span>channels <span style="color:#555">+</span> <span style="color:#f60">1</span>]; <span style="color:#09f;font-style:italic">// G
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#09f;font-style:italic"></span>			<span style="color:#078;font-weight:bold">int</span> r <span style="color:#555">=</span> pixelPtr[x<span style="color:#555">*</span>frame.cols<span style="color:#555">*</span>channels <span style="color:#555">+</span> y<span style="color:#555">*</span>channels <span style="color:#555">+</span> <span style="color:#f60">2</span>]; <span style="color:#09f;font-style:italic">// R
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#09f;font-style:italic"></span>			printf(<span style="color:#c30">&#34;</span><span style="color:#c30;font-weight:bold">\033</span><span style="color:#c30">[48;2;%d;%d;%dm &#34;</span>,r,g,b);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>		}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>		<span style="color:#069;font-weight:bold">if</span>(y <span style="color:#555">!=</span> termHeight<span style="color:#555">-</span><span style="color:#f60">2</span>) {cout <span style="color:#555">&lt;&lt;</span> <span style="color:#c30">&#34;</span><span style="color:#c30;font-weight:bold">\n</span><span style="color:#c30">&#34;</span>;}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>	}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>	printf(<span style="color:#c30">&#34;</span><span style="color:#c30;font-weight:bold">\033</span><span style="color:#c30">[0;0;H&#34;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>	printf(<span style="color:#c30">&#34;</span><span style="color:#c30;font-weight:bold">\033</span><span style="color:#c30">[48;2;0;0;0m&#34;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>	printf(<span style="color:#c30">&#34;</span><span style="color:#c30;font-weight:bold">\033</span><span style="color:#c30">[38;2;0;0;0m&#34;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>}
</span></span></code></pre></div><p>I messed a bit with unicode characters to try do double the resolution but it isn't working really well</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_68747470733a2f2f692e696d6775722e636f6d2f66696551615a492e706e67.png" >
            <img alt="" src="/terminal-tv-watching-videos-on-the-terminal/images/_huc6f1f12607e4a33ed2f3cc8efd1e2bda_52137_2047b3152533e9913021176c4ee609e5.webp" />
        </a>
        
    </figure>

</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/terminal_tv" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/f9327ac48be33f4251c2ea1e35332b98_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/f9327ac48be33f4251c2ea1e35332b98_huac2666a4c21caa7f6a28cf0200f3b835_0_0x720_resize_q90_h2_box_3.webp' alt='Watch any mp4 video in a terminal (Using ansi color codes so no sound) - TheStaticTurtle/terminal_tv'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/terminal_tv">GitHub - TheStaticTurtle/terminal_tv: Watch any mp4 video in a terminal (Using ansi color codes so no sound)</a>
        
        
            <p>Watch any mp4 video in a terminal (Using ansi color codes so no sound) - TheStaticTurtle/terminal_tv</p>
        
    </div>
</blockquote>
        
    


    
        

        
            


<div style="display: flex; justify-content: center;">
    <iframe 
        src="https://www.youtube-nocookie.com/embed/YMIr55X8WbQ"
        width="720"
        height="406"
        title="YouTube video player"
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowfullscreen
    ></iframe>
</div>

 ]]></content:encoded></item><item><title>LinkyLink - Connecting myself to the French energy meter</title><description> &lt;p>Making a custom board to connect an esp8266 to the french energy meter&lt;/p></description><link>https://blog.thestaticturtle.fr/linkylink-connecting-myself-to-the-french-energy-meter/</link><guid>https://blog.thestaticturtle.fr/linkylink-connecting-myself-to-the-french-energy-meter/</guid><category> Diy</category><category> Electronics</category><category> Iot</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 14 Jan 2020 20:11:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/linkylink-connecting-myself-to-the-french-energy-meter/images/cover.jpg"/><media:content url="https://blog.thestaticturtle.fr/linkylink-connecting-myself-to-the-french-energy-meter/images/cover.jpg" medium="image"/><content:encoded><![CDATA[ <h1>LinkyLink - Connecting myself to the French energy meter</h1>
<span class="subtitle"><p>Making a custom board to connect an esp8266 to the french energy meter</p></span>
<br>

    <img class="" src='/linkylink-connecting-myself-to-the-french-energy-meter/images/cover_huf45f9820b58e8c77454f1d5ec1480453_16927_1350x900_fit_q80_box.jpg' alt="LinkyLink - Connecting myself to the French energy meter"/>

<hr>

<h2 id="a-bit-of-context">
    A bit of context 
    
    <a class="header-link" href="#a-bit-of-context">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_4f7d74837884ae09d9a3a88a609faa01e80c6ea8-compteur-linky-teleinformation.jpeg">
                            <img src="/linkylink-connecting-myself-to-the-french-energy-meter/images/dl_4f7d74837884ae09d9a3a88a609faa01e80c6ea8-compteur-linky-teleinformation.jpeg" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/dl_e774561f541de60f5a360a97c188bf3412fc1ae2-compteurlinky-e15499842838113x.jpeg">
                            <img src="/linkylink-connecting-myself-to-the-french-energy-meter/images/dl_e774561f541de60f5a360a97c188bf3412fc1ae2-compteurlinky-e15499842838113x.jpeg" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>
<p>The Linky energy meter is &quot;connected&quot; energy meter it transmit the energy consumed by their user to the energy company (Enedis).</p>
<h2 id="the-inner-workings">
    The inner workings 
    
    <a class="header-link" href="#the-inner-workings">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The arrow on the second picture above show the user teleinfo port. Here the user is free to plug something like the &quot;Linky ERL&quot; or any device to monitor the meter</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_Screenshot_20200121_222732.png" >
            <img alt="" src="/linkylink-connecting-myself-to-the-french-energy-meter/images/dl_Screenshot_20200121_222732.png" />
        </a>
        
    </figure>

</p>
<p>The linky provides 3 &quot;easy&quot; to use ports I1 I2 and A. The actual data comes from the circuit I1 and I2 and you have an alimentation circuit between I1 and A. For reference P, N, P', N' are the mains connections C1 and C2 connection to a water boiler and T1 / T2 are I think for technicians and are locked.</p>
<p>The actual signal is a unidirectional serial connection with a 50kHz carrier. The documentation (available at the bottom) says that the signal is at 9600bps 7E1 but the signal is actually 1200bps at 7E1. Converting this signal is really simple I used the following circuit:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_Screenshot_20200121_224123.png" >
            <img alt="" src="/linkylink-connecting-myself-to-the-french-energy-meter/images/dl_Screenshot_20200121_224123.png" />
        </a>
        
    </figure>

</p>
<p>The linky spits out frames composed of these lines (For the one I have):</p>
<pre tabindex="0"><code>ADCO &lt;censored&gt;
OPTARIF HC..
ISOUSC 30
HCHC 000582078
HCHP 000599002
PTEC HP..
IINST 005
IMAX 090
PAPP 01115
HHPHC A
MOTDETAT 000000
</code></pre><p>Here's a table to see what stands for what:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_Screenshot_20200121_225030-1@2x.png" >
            <img alt="" src="/linkylink-connecting-myself-to-the-french-energy-meter/images/dl_Screenshot_20200121_225030-1@2x_hucaf481aa35e33f3ff9ae51a4fd6530fd_77507_0x720_resize_q90_h2_box_3.webp" />
        </a>
        
    </figure>

</p>
<h2 id="version-1---prototype">
    Version 1 - Prototype 
    
    <a class="header-link" href="#version-1---prototype">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>The documentation tells us how a frame is transmitted: First you have the char 0x02 there for each line start 0x0A and 0x0D the end of a line the end of a frame is signaled by the char 0x03 and a premature end of data by 0x04</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-h" data-lang="h"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1</span><span><span style="color:#099">#define LINKY_START_FRAME           0x02
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2</span><span><span style="color:#099">#define LINKY_END_FRAME             0x03
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3</span><span><span style="color:#099">#define LINKY_START_LINE            0x0A
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4</span><span><span style="color:#099">#define LINKY_END_LINE              0x0D
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5</span><span><span style="color:#099">#define LINKY_END_DATA              0x04
</span></span></span></code></pre></div><p>As I wanted to make my module compatible with 3-phased devices I added all these in the code as well and here's my structure for holding the data:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#069;font-weight:bold">struct</span> <span style="color:#0a8;font-weight:bold">LinkyData</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>  <span style="color:#078;font-weight:bold">char</span>   ADCO   [<span style="color:#f60">13</span>];      <span style="color:#09f;font-style:italic">//Adresse du compteur
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   OPTARIF[ <span style="color:#f60">5</span>];      <span style="color:#09f;font-style:italic">//Option tarifaire choisie
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    ISOUSC;           <span style="color:#09f;font-style:italic">//Intensitée souscrite
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    BASE;             <span style="color:#09f;font-style:italic">//Index option Base
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   HCHC;             <span style="color:#09f;font-style:italic">//Index option Heures Creuses - Heures Creuses
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   HCHP;             <span style="color:#09f;font-style:italic">//Index option Heures Creuses - Heures Pleines
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   EJPHN;            <span style="color:#09f;font-style:italic">//Index option EJP - Heures Normales
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   EJPHPM;           <span style="color:#09f;font-style:italic">//Index option EJP - Heures de Pointe Mobile
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHCJB;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Creuses Jours Bleus
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHPJB;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Pleines Jours Bleus
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHCJW;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Creuses Jours Blancs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHPJW;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Pleines Jours Blancs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHCJR;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Pleines Jours Rouges
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   BBRHPJR;          <span style="color:#09f;font-style:italic">//Index option Tempo - Heures Pleines Jours Rouges
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    PEJP;             <span style="color:#09f;font-style:italic">//Préavis Début EJP (30 min)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   PTEC   [ <span style="color:#f60">5</span>];      <span style="color:#09f;font-style:italic">//Période Tarifaire en cours
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   DEMAIN [ <span style="color:#f60">5</span>];      <span style="color:#09f;font-style:italic">//Couleur du lendemain
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IINST;            <span style="color:#09f;font-style:italic">//Intensité Instantanée
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IINST1;           <span style="color:#09f;font-style:italic">//Intensité Instantanée Phase né1 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IINST2;           <span style="color:#09f;font-style:italic">//Intensité Instantanée Phase né2 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IINST3;           <span style="color:#09f;font-style:italic">//Intensité Instantanée Phase né3 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    ADPS;             <span style="color:#09f;font-style:italic">//Avertissement de DépassementDe Puissance Souscrite
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    ADIR1;            <span style="color:#09f;font-style:italic">//Avertissement de DépassementDe Puissance Souscrite Phase né1 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    ADIR2;            <span style="color:#09f;font-style:italic">//Avertissement de DépassementDe Puissance Souscrite Phase né2 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    ADIR3;            <span style="color:#09f;font-style:italic">//Avertissement de DépassementDe Puissance Souscrite Phase né3 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IMAX;             <span style="color:#09f;font-style:italic">//Intensité maximale appelée
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IMAX1;            <span style="color:#09f;font-style:italic">//Intensité maximale appelée Phase né1 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IMAX2;            <span style="color:#09f;font-style:italic">//Intensité maximale appelée Phase né2 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">int</span>    IMAX3;            <span style="color:#09f;font-style:italic">//Intensité maximale appelée Phase né3 (Triphaser seulement)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   PMAX;             <span style="color:#09f;font-style:italic">//Puissance maximale triphasée atteinte
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">long</span>   PAPP;             <span style="color:#09f;font-style:italic">//Puissance apparente / Puissance apparente triphasée soutirée
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   HHPHC;            <span style="color:#09f;font-style:italic">//Horaire Heures Pleines Heures Creuses
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   MOTDETAT[ <span style="color:#f60">7</span>];     <span style="color:#09f;font-style:italic">//Mot d&#39;état du compteur
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span><span style="color:#09f;font-style:italic"></span>  <span style="color:#078;font-weight:bold">char</span>   PPOT    [ <span style="color:#f60">3</span>];     <span style="color:#09f;font-style:italic">//Présence des potentiels (Triphaser seulement) (&#34;0X&#34;, X = coupures de phase phase n =&gt; bit n = 1)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span><span style="color:#09f;font-style:italic"></span>}; 
</span></span></code></pre></div><p>Receiving the data is as simple as creating a new software serial with the right pins and the incoming byte by 0x7F to get only the 7 bits since arduino software serial can't strictly do 7E1 communications. Then I created a char array of the size of 100 (It will never be used completely) and incremented a variable at each valid char received to store it.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">void</span> Linky<span style="color:#555">::</span>processRXChar(<span style="color:#078;font-weight:bold">char</span> currentChar) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>    <span style="color:#069;font-weight:bold">if</span>(currentChar <span style="color:#555">==</span> LINKY_START_FRAME) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        memset(_buffer, <span style="color:#f60">0</span>, LINKY_BUFFER_TELEINFO_SIZE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        _bufferIterator <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>    } <span style="color:#069;font-weight:bold">else</span> <span style="color:#c0f">if</span>(currentChar <span style="color:#555">==</span> LINKY_START_LINE) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>        memset(_buffer, <span style="color:#f60">0</span>, LINKY_BUFFER_TELEINFO_SIZE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>        _bufferIterator <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>    } <span style="color:#069;font-weight:bold">else</span> <span style="color:#c0f">if</span>(currentChar <span style="color:#555">==</span> LINKY_END_FRAME) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>        memset(_buffer, <span style="color:#f60">0</span>, LINKY_BUFFER_TELEINFO_SIZE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>        _bufferIterator <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>    } <span style="color:#069;font-weight:bold">else</span> <span style="color:#c0f">if</span>(currentChar <span style="color:#555">==</span> LINKY_END_LINE) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>        updateStruct(_bufferIterator);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>        memset(_buffer, <span style="color:#f60">0</span>, LINKY_BUFFER_TELEINFO_SIZE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>        _bufferIterator <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>    } <span style="color:#069;font-weight:bold">else</span> <span style="color:#c0f">if</span>(currentChar <span style="color:#555">==</span> LINKY_END_DATA) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>        memset(_buffer, <span style="color:#f60">0</span>, LINKY_BUFFER_TELEINFO_SIZE);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span>        _bufferIterator <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>    } <span style="color:#069;font-weight:bold">else</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>        _buffer[_bufferIterator] <span style="color:#555">=</span> currentChar;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>        _bufferIterator<span style="color:#555">++</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span><span style="color:#078;font-weight:bold">void</span> Linky<span style="color:#555">::</span>updateAsync() {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>    <span style="color:#069;font-weight:bold">if</span>(_serport<span style="color:#555">-&gt;</span>available()) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span>        <span style="color:#078;font-weight:bold">char</span> currentChar;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>        currentChar <span style="color:#555">=</span> _serport<span style="color:#555">-&gt;</span>read() <span style="color:#555">&amp;</span> <span style="color:#f60">0x7F</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>        processRXChar(currentChar);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span>}
</span></span></code></pre></div><p>Then I created a couple of function that ease the parsing of the data by a lot (getCommandValue_int / getCommandValue_long / getCommandValue_str) to use these function you have to pass the actual command name like ADCO the length in this case 4 and the expected length of the output in this case 12 plus some other values like the size of received line.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">bool</span> Linky<span style="color:#555">::</span>isValidNumber(String str){
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>   <span style="color:#069;font-weight:bold">for</span>(byte i<span style="color:#555">=</span><span style="color:#f60">0</span>;i<span style="color:#555">&lt;</span>str.length();i<span style="color:#555">++</span>)  {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>       <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>isDigit(str.charAt(i))) <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">false</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>   }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>   <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">true</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span><span style="color:#078;font-weight:bold">int</span> Linky<span style="color:#555">::</span>getCommandValue_int(String CMD, <span style="color:#078;font-weight:bold">int</span> CMDlenght, <span style="color:#078;font-weight:bold">int</span> CMDResultLenght, String line, <span style="color:#078;font-weight:bold">int</span> lineLenght, <span style="color:#078;font-weight:bold">int</span> defaultIfError) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>  <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>line.startsWith(CMD)) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>  <span style="color:#069;font-weight:bold">if</span>(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght <span style="color:#555">&gt;</span> lineLenght) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; } <span style="color:#09f;font-style:italic">// Invalid size line length too short
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span><span style="color:#09f;font-style:italic"></span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>  String raw_value <span style="color:#555">=</span> line.substring(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span>, CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>  <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>isValidNumber(raw_value)) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>  <span style="color:#069;font-weight:bold">return</span> raw_value.toInt();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18</span><span><span style="color:#078;font-weight:bold">long</span> Linky<span style="color:#555">::</span>getCommandValue_long(String CMD, <span style="color:#078;font-weight:bold">int</span> CMDlenght, <span style="color:#078;font-weight:bold">int</span> CMDResultLenght, String line, <span style="color:#078;font-weight:bold">int</span> lineLenght, <span style="color:#078;font-weight:bold">long</span> defaultIfError) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19</span><span>  <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>line.startsWith(CMD)) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20</span><span>  <span style="color:#069;font-weight:bold">if</span>(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght <span style="color:#555">&gt;</span> lineLenght) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; } <span style="color:#09f;font-style:italic">// Invalid size line length too short
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21</span><span><span style="color:#09f;font-style:italic"></span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22</span><span>  String raw_value <span style="color:#555">=</span> line.substring(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span>, CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23</span><span>  <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>isValidNumber(raw_value)) { <span style="color:#069;font-weight:bold">return</span> defaultIfError; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25</span><span>  <span style="color:#069;font-weight:bold">return</span> (<span style="color:#078;font-weight:bold">long</span>)raw_value.toInt();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28</span><span><span style="color:#078;font-weight:bold">bool</span> Linky<span style="color:#555">::</span>getCommandValue_str(String CMD, <span style="color:#078;font-weight:bold">int</span> CMDlenght, <span style="color:#078;font-weight:bold">int</span> CMDResultLenght, String line, <span style="color:#078;font-weight:bold">int</span> lineLenght, <span style="color:#078;font-weight:bold">char</span><span style="color:#555">*</span> value) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29</span><span>  <span style="color:#069;font-weight:bold">if</span>(<span style="color:#555">!</span>line.startsWith(CMD)) { <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">false</span>; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30</span><span>  <span style="color:#069;font-weight:bold">if</span>(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght <span style="color:#555">&gt;</span> lineLenght) { <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">false</span>; } <span style="color:#09f;font-style:italic">// Invalid size line length too short
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31</span><span><span style="color:#09f;font-style:italic"></span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32</span><span>  line.substring(CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span>, CMDlenght<span style="color:#555">+</span><span style="color:#f60">1</span><span style="color:#555">+</span>CMDResultLenght).toCharArray(value,CMDResultLenght<span style="color:#555">+</span><span style="color:#f60">1</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33</span><span>  <span style="color:#069;font-weight:bold">return</span> <span style="color:#366">true</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34</span><span>} 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36</span><span><span style="color:#078;font-weight:bold">void</span> Linky<span style="color:#555">::</span>updateStruct(<span style="color:#078;font-weight:bold">int</span> len) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37</span><span>     getCommandValue_str (<span style="color:#c30">&#34;ADCO&#34;</span>    , <span style="color:#f60">4</span>,<span style="color:#f60">12</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ADCO     );
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38</span><span>     getCommandValue_str (<span style="color:#c30">&#34;OPTARIF&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">4</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.OPTARIF  );
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39</span><span>    _data.ISOUSC    <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;ISOUSC&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">2</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ISOUSC);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40</span><span>    _data.HCHC      <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;HCHC&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.HCHC);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41</span><span>    _data.HCHP      <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;HCHP&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.HCHP);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42</span><span>    _data.EJPHN     <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;EJPHN&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.EJPHN);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43</span><span>    _data.EJPHPM    <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;EJPHPM&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.EJPHPM);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44</span><span>    _data.BBRHCJB   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHCJB&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHCJB);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45</span><span>    _data.BBRHPJB   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHPJB&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHPJB);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46</span><span>    _data.BBRHCJW   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHCJW&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHCJW);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47</span><span>    _data.BBRHPJW   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHPJW&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHPJW);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48</span><span>    _data.BBRHCJR   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHCJR&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHCJR);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49</span><span>    _data.BBRHPJR   <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;BBRHPJR&#34;</span> , <span style="color:#f60">7</span>, <span style="color:#f60">9</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.BBRHPJR);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50</span><span>    _data.PEJP      <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;PEJP&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">2</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.HCHP);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51</span><span>     getCommandValue_str (<span style="color:#c30">&#34;PTEC&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">4</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.PTEC  );
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52</span><span>     getCommandValue_str (<span style="color:#c30">&#34;DEMAIN&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">4</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.DEMAIN  );
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53</span><span>    _data.IINST     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IINST&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IINST); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54</span><span>    _data.IINST1    <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IINST1&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IINST1); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55</span><span>    _data.IINST2    <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IINST2&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IINST2); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56</span><span>    _data.IINST3    <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IINST3&#34;</span>  , <span style="color:#f60">6</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IINST3); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57</span><span>    _data.ADPS      <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;ADPS&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ADPS); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58</span><span>    _data.ADIR1     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;ADIR1&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ADIR1); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59</span><span>    _data.ADIR2     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;ADIR2&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ADIR2); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60</span><span>    _data.ADIR3     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;ADIR3&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.ADIR3);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61</span><span>    _data.IMAX      <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IMAX&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IMAX); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62</span><span>    _data.IMAX1     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IMAX1&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IMAX1); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">63</span><span>    _data.IMAX2     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IMAX2&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IMAX2); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">64</span><span>    _data.IMAX3     <span style="color:#555">=</span>   getCommandValue_int (<span style="color:#c30">&#34;IMAX3&#34;</span>   , <span style="color:#f60">5</span>, <span style="color:#f60">3</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.IMAX3); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">65</span><span>    _data.PMAX      <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;PMAX&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">5</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.PMAX); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">66</span><span>    _data.PAPP      <span style="color:#555">=</span>   getCommandValue_long(<span style="color:#c30">&#34;PAPP&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">5</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.PAPP); 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">67</span><span>     getCommandValue_str (<span style="color:#c30">&#34;MOTDETAT&#34;</span>, <span style="color:#f60">8</span>, <span style="color:#f60">6</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.MOTDETAT );
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">68</span><span>     getCommandValue_str (<span style="color:#c30">&#34;PPOT&#34;</span>    , <span style="color:#f60">4</span>, <span style="color:#f60">2</span>,_buffer,len<span style="color:#555">+</span><span style="color:#f60">1</span>, _data.PPOT);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">69</span><span>}
</span></span></code></pre></div><p>Implementing everything into the ESP8266:</p>

    
        

        
            









<blockquote class="embed embed-github">
    
        
            <a target="_blank" href="https://github.com/TheStaticTurtle/LinkyLink" class="embed-image-container">
                <div class='embed-image-background' style='background-image: url(/images/614fe78f54194e297717cd10cac07547_hu192638f96fcac574ebbb22f83c480cb8_0_0x720_resize_q90_h2_box.webp);'></div>

                <div class="embed-image-image">
                    <img src='/images/614fe78f54194e297717cd10cac07547_hu192638f96fcac574ebbb22f83c480cb8_0_0x720_resize_q90_h2_box.webp' alt='A wireless adapter for the french energy meter (Linky) based on an esp8266. - TheStaticTurtle/LinkyLink'>
                </div>
            </a>
        
    
    <div class="embed-content">
        
            <a target="_blank" class="embed-title" href="https://github.com/TheStaticTurtle/LinkyLink">GitHub - TheStaticTurtle/LinkyLink: A wireless adapter for the french energy meter (Linky) based on an esp8266.</a>
        
        
            <p>A wireless adapter for the french energy meter (Linky) based on an esp8266. - TheStaticTurtle/LinkyLink</p>
        
    </div>
</blockquote>
        
    

<p>The code is working on some meters but on some other I cannot get data to be read by the esp8266 it seems to be an optocoupler issue.</p>

 ]]></content:encoded></item><item><title>Bone conduction glasses</title><description> &lt;p>I had a really boring class in high school and short story I made bone conduction glasses&lt;/p></description><link>https://blog.thestaticturtle.fr/bone-conduction-glasses/</link><guid>https://blog.thestaticturtle.fr/bone-conduction-glasses/</guid><category> Diy</category><category> Electronics</category><category> 3 d</category><dc:creator> Samuel</dc:creator><pubDate>Tue, 09 Apr 2019 20:11:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/bone-conduction-glasses/images/cover.jpg"/><media:content url="https://blog.thestaticturtle.fr/bone-conduction-glasses/images/cover.jpg" medium="image"/><content:encoded><![CDATA[ <h1>Bone conduction glasses</h1>
<span class="subtitle"><p>I had a really boring class in high school and short story I made bone conduction glasses</p></span>
<br>

    <img class="" src='/bone-conduction-glasses/images/cover_hu0c318db8764c4ab49e971ac7c44279a5_26869_1350x900_fit_q80_box.jpg' alt="Bone conduction glasses"/>

<hr>

<h2 id="why-and-how">
    Why and how 
    
    <a class="header-link" href="#why-and-how">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>I had a really boring class in high school and I've seen product pages of bone conduction glasses and boy they were expensive. So I m bought two bone conduction modules from AliExpress (The GD-02 model) the module itself is about 12.6<em>6</em>4mm.</p>

    
        <blockquote class="embed">
            <div class="embed-content">
                <a target="_blank" href="https://aliexpress.com/item/32963777242.html">https://aliexpress.com/item/32963777242.html</a>
            </div>
        </blockquote>
    

<p>Next I had to search for a Bluetooth module to be able to receive the music. I tried to search for an ic that I could put on a pcb but a friend had his old wireless earphones broken, fortunately the receiver part and the battery still works, so I used that</p>
<p>So this has taken so long to make everything fit inside and make the glasses a minimum stylish / comfortable. All the design have been created using SolidWorks.</p>
<p>I don't have screenshot of all the diff rents versions I went through but after more the 50 hours in SolidWorks I came up with this model:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_sldworks_2019-04-09_16-23-13_aEK8dUHxTt.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_sldworks_2019-04-09_16-23-13_aEK8dUHxTt.jpg" />
        </a>
        
    </figure>

</p>
<p>After some 3d printing the part came out pretty nice:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_163118_AO6ltaO0Iq.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_163118_AO6ltaO0Iq.jpg" />
        </a>
        
    </figure>

</p>
<p>The 3d model is open source on <a target="_blank" href="https://www.thingiverse.com/thing:3552193">Thingiverse</a></p>
<h2 id="wiring">
    Wiring 
    
    <a class="header-link" href="#wiring">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>First I had to find a way to connect the battery and the left module (Which is on the left branch) to the right branch. The only way that I found to make this work is to get some old Chinese ear buds cut the cable remove the rubber insulation and you got 4 isolated wire that is very tiny.</p>
<p>To not make a mess when assembling later on I braided the 4 cables together and cut them to the appropriate size.</p>
<p>Then I disconnected the original wire that were on the battery and replace them with my own wires.</p>
<h2 id="assembly">
    Assembly 
    
    <a class="header-link" href="#assembly">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>Assembly was tricky first I need to pass the cable trough the left branch hole, next I need to pass the left speaker cable to the correct hole and the solder the module. Next I can put the battery inside the hole and close the lid with 2 screws.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_142421_84Ulm7yRcA.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_142421_84Ulm7yRcA.jpg" />
        </a>
        
    </figure>

</p>
<p>Next you need to pass the cable in the middle hole of center part of the glasses</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_140140_yV4d1tTUWc.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_140140_yV4d1tTUWc.jpg" />
        </a>
        
    </figure>

</p>
<p>Next we can assemble the right branch. First cut thin cable to solder to the right module and solder them to it, do the same for the on/off/recall button then cut the wires to size and solder them to the module:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_140054_0X3QsPv0Xt.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_140054_0X3QsPv0Xt.jpg" />
        </a>
        
    </figure>

</p>
<p>Then trim the wire comming from the left branch while being VERY CAREFUL to not short the battery wire pass them to the hole and solder it to the main board.</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_143456_en1dpu5PSl.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_143456_en1dpu5PSl.jpg" />
        </a>
        
    </figure>

</p>
<p>Then put a piece of solid wire to fix the branches to the main body</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190409_153051_qBiHL759IC.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190409_153051_qBiHL759IC.jpg" />
        </a>
        
    </figure>

</p>
<p>Next I placed my old correction lenses on the assembly and after an overnight charge and enjoy music. I repeat, it's not very loud but still audible and lacks a bit of bass but overall a great project. Also, I didn't expect this but there is not a ton of sound leakage it's relatively quiet. And some other pictures to finish the post:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_img_20190408_174657_uZ2NPqoMP1.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_img_20190408_174657_uZ2NPqoMP1.jpg" />
        </a>
        
    </figure>

</p>
<p>After discussing my project with my high school mechanical teacher he agreed to print it on the school powder printer and here is the final version:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_IMG_20190525_191733_Bokeh.jpg" >
            <img alt="" src="/bone-conduction-glasses/images/dl_IMG_20190525_191733_Bokeh_hu9893c6b51ff5bc217c8ea38001ded935_297728_0x720_resize_q90_h2_box.webp" />
        </a>
        
    </figure>

</p>

 ]]></content:encoded></item><item><title>TI-NSpire SMS Shield</title><description> &lt;p>Connecting my Ti-Nspire to the world of sms&lt;/p></description><link>https://blog.thestaticturtle.fr/ti-nspire-sms-shield/</link><guid>https://blog.thestaticturtle.fr/ti-nspire-sms-shield/</guid><category> Diy</category><category> Electronics</category><category> Iot</category><dc:creator> Samuel</dc:creator><pubDate>Thu, 25 Jan 2018 20:11:00 +0000</pubDate><media:thumbnail url="https://blog.thestaticturtle.fr/ti-nspire-sms-shield/images/cover_hu373c9665ca419c127f5f9b095e056607_232313_0x720_resize_q90_h2_box.webp"/><media:content url="https://blog.thestaticturtle.fr/ti-nspire-sms-shield/images/cover_hu373c9665ca419c127f5f9b095e056607_232313_0x720_resize_q90_h2_box.webp" medium="image"/><content:encoded><![CDATA[ <h1>TI-NSpire SMS Shield</h1>
<span class="subtitle"><p>Connecting my Ti-Nspire to the world of sms</p></span>
<br>

    <img class="" src='/ti-nspire-sms-shield/images/cover_hu373c9665ca419c127f5f9b095e056607_232313_1350x900_fit_q80_box.jpg' alt="TI-NSpire SMS Shield"/>

<hr>

<h2 id="why-and-how">
    Why and how 
    
    <a class="header-link" href="#why-and-how">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>
<p>It all started in a German class I had. I was talking to a friend, and we said that it could be awesome to send sms with our TI. After one-two months, I started to do some digging and found out about some &quot;shield&quot; that could be snapped into the bottom connector of the nSpire. After googling a bit I found out that a few people actually did research on this, so this is what I did and here are the 4 pins that I'm interested in:</p>
<p>
    <figure>
        <a target="_blank" href="images/dl_ob_299a6c_dockconnectorw.jpg" >
            <img alt="" src="/ti-nspire-sms-shield/images/dl_ob_299a6c_dockconnectorw.jpg" />
        </a>
        
    </figure>

</p>
<p>After quickly soldering some wires to it and connecting a FTDI breakout board to it, I reset the calculator, and it started dumping out the boot log, so that told me that this could actually work. Now I need a way to access it in software. I searched a bit and found ndless. It's mainly used to play games, but a full SDK to cross compile c for the nSpire. Next I found the nspire-io lib after testing I found that sending was ok the receiving was a pain due to some compatibility issue with different models of TI. After speaking to the author and some nice peoples on the codewalr forum, I modified the source code and it worked. After re-creating a function, it allowed me to receive data until a newline (\n)</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1</span><span><span style="color:#078;font-weight:bold">char</span><span style="color:#555">*</span> <span style="color:#c0f">uart_getsn</span>(<span style="color:#078;font-weight:bold">char</span><span style="color:#555">*</span> str, <span style="color:#078;font-weight:bold">int</span> num) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2</span><span>        <span style="color:#078;font-weight:bold">int</span> i <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3</span><span>        <span style="color:#078;font-weight:bold">int</span> max <span style="color:#555">=</span> num<span style="color:#555">-</span><span style="color:#f60">1</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4</span><span>        <span style="color:#069;font-weight:bold">while</span>(<span style="color:#f60">1</span> <span style="color:#555">&amp;&amp;</span> <span style="color:#c0f">releasefunc</span>()) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5</span><span>                <span style="color:#069;font-weight:bold">while</span>(<span style="color:#c0f">uart_ready</span>() <span style="color:#555">&amp;&amp;</span> <span style="color:#c0f">isKeyPressed</span>(KEY_NSPIRE_ESC)) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6</span><span>                        <span style="color:#069;font-weight:bold">if</span>(i <span style="color:#555">==</span> max) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7</span><span>                                str[i] <span style="color:#555">=</span> <span style="color:#f60">0</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8</span><span>                                <span style="color:#069;font-weight:bold">return</span> str;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9</span><span>                        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10</span><span>                        <span style="color:#078;font-weight:bold">char</span> c <span style="color:#555">=</span> <span style="color:#c0f">uart_getchar</span>();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11</span><span>                        str[i] <span style="color:#555">=</span> c;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12</span><span>                        <span style="color:#069;font-weight:bold">if</span>(c <span style="color:#555">==</span> <span style="color:#c30">&#39;\b&#39;</span>){  i <span style="color:#555">-=</span> <span style="color:#f60">2</span>; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13</span><span>                        <span style="color:#069;font-weight:bold">else</span> <span style="color:#069;font-weight:bold">if</span>(c <span style="color:#555">==</span> <span style="color:#c30">&#39;\r&#39;</span>)      { str[i] <span style="color:#555">=</span><span style="color:#f60">0</span>; <span style="color:#069;font-weight:bold">return</span> str; }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14</span><span>                        i<span style="color:#555">++</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15</span><span>                }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17</span><span>}
</span></span></code></pre></div><p>After doing some CAD I had it all working and well. Here are source ode link and cad files: <a target="_blank" href="https://github.com/TheStaticTurtle/nspire-communication">GITHUB</a></p>
<h2 id="photos">
    Photos 
    
    <a class="header-link" href="#photos">
        <i class="mdi mdi-link-variant" aria-hidden="true"></i>
    </a>
    
</h2>


    
    

    
    
        
    

    
    
        
    

    
    




<div class="img-gallery-container">
    
        
        

        <div class="img-gallery-row">
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20180422_230404.jpg">
                            <img src="/ti-nspire-sms-shield/images/IMG_20180422_230404_hu216b6c8aa7adb0cb9806f51ec5a4b6b9_491851_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        

        
    
        
        

        
            
            
            
            
            

            
                <div class="img-gallery-image">
                    <figure>
                        <a target="_blank" href="images/IMG_20180422_230429.jpg">
                            <img src="/ti-nspire-sms-shield/images/IMG_20180422_230429_hu373c9665ca419c127f5f9b095e056607_232313_0x720_resize_q90_h2_box.webp" alt="">
                        </a>
                        
                    </figure>
                </div>
            
            
        </div>

        
    
</div>

 ]]></content:encoded></item></channel></rss>