<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Jean-Francois&#39;s blog</title>
<link>https://blog.jean-francois.im/</link>
<atom:link href="https://blog.jean-francois.im/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<generator>quarto-1.8.27</generator>
<lastBuildDate>Tue, 12 May 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>How I Use LLMs to Write Code in 2026</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/</link>
  <description><![CDATA[ 






<p>A few friends have asked how I use LLMs to write code these days, so I decided I’d write this blog post to share my current setup. There may be better ways to use them, but this is what I found works well for me.</p>
<p>First of all, I use the $100/mo <a href="https://claude.com/pricing/max">Claude Max</a> subscription. I’ve heard that <a href="https://developers.openai.com/codex/pricing">OpenAI’s Codex</a> works pretty well these days too, though I haven’t evaluated it lately. Last time I evaluated Gemini/Antigravity, it was complete garbage for writing code and a waste of time; this may or may not be the case anymore.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>The $20/month subscriptions from Anthropic and OpenAI might work as well for lighter use. I frequently brush against the five hour limit, your mileage may vary.</p>
</div></div><p>Currently, in 2026, frontier models significantly outperform locally hosted models for agentic software development. This may change in the coming years, but today, subscriptions are the way to go. Of course, if you have money, you could run say <a href="https://huggingface.co/unsloth/Kimi-K2.6-GGUF">Kimi K2.6</a> locally. At 1 trillion parameters, it’ll take over 512GB of memory, you’ll need at least two 512GB Mac Studios or four 256GB Mac Studios — which are <a href="https://www.macobserver.com/news/apple-removes-256gb-m3-ultra-mac-studio-model-from-online-store/">now unavailable due to the RAM shortage,</a> but even prior to that the 512GB ones were listed for $9,500 each — and still run at a slower speed than cloud hosted models.</p>
<p>You can use either the desktop version of Claude code or the CLI one. I used the CLI one a lot more in the past, but the desktop one makes it much easier to run multiple sessions in parallel.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Anthropic doesn’t currently ship a desktop version for Linux, so you’ll need <a href="https://github.com/aaddrick/claude-desktop-debian">claude-desktop-debian</a> if you’re on Ubuntu/Debian.</p>
</div></div><div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/claude-code.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Claude Code desktop screenshot"><img src="https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/images/claude-code.png" class="img-fluid figure-img" alt="Claude Code desktop screenshot"></a></p>
<figcaption>Claude Code desktop screenshot</figcaption>
</figure>
</div>
<p>The first thing to do is to install a few plugins by clicking customize or <code>/plugins</code> in the CLI. The superpowers one makes it so that when talking about a new feature, Claude will enter brainstorm mode and ask clarifying questions about what is being built, then propose a plan. You should definitely push back on the plan if it’s not what you want, or if it’s missing something. Context7 and a LSP for your language are also pretty useful.</p>
<p>By default, Claude will run in permissions restricted mode, which means it’ll <em>constantly</em> bug you about permissions for anything more than editing a file in the project directory. Build the project? Prompt. Run the linter? Prompt. Curl for the documentation for a library? Prompt. In practice, you probably want to run in either “Auto” or “Disable permissions” mode.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>This also means that Claude can run pretty much whatever on your computer, so if someone on the internet writes that to install <code>libfoo</code> you should do <code>rm  -rf /</code> it might actually do it.</p>
</div></div><p>So now that we have our environment set up, it’s time to create a project. To do so:</p>
<ul>
<li>Create a project on github (I’ll use <a href="https://github.com/jfim/cham-chrome-extension">cham-chrome-extension</a> for this demo)</li>
<li>Create an empty directory on your computer and run <code>git init</code> in it</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/terminal.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Terminal with an empty git repo, ready for Claude"><img src="https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/images/terminal.png" class="img-fluid figure-img" alt="Terminal with an empty git repo, ready for Claude"></a></p>
<figcaption>Terminal with an empty git repo, ready for Claude</figcaption>
</figure>
</div>
<p>With that, it’s time to prompt the LLM to start writing code.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Another approach is to write a more detailed markdown document and ask the LLM to read it. This is a better approach if what you want is a bit too long to fit in just a simple text box, unlike what this demonstration project requires.</p>
</div></div><div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/prompt.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="The initial prompt to Claude Code"><img src="https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/images/prompt.png" class="img-fluid figure-img" alt="The initial prompt to Claude Code"></a></p>
<figcaption>The initial prompt to Claude Code</figcaption>
</figure>
</div>
<p>It’s usually a good idea to have at least some basic quality control like linting, auto formatting, and testing right off the bat. LLMs are pretty good at fixing errors when they are pointed out to them, so this makes sure that the code is at least formatted correctly and covered by tests.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/working.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Claude Code working on the project"><img src="https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/images/working.png" class="img-fluid figure-img" alt="Claude Code working on the project"></a></p>
<figcaption>Claude Code working on the project</figcaption>
</figure>
</div>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/iteration.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Discussing the design"><img src="https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/images/iteration.png" class="img-fluid figure-img" alt="Discussing the design"></a></p>
<figcaption>Discussing the design</figcaption>
</figure>
</div>
<p>After some back and forth, it’ll generate a plan, then actually implement it.</p>
<p>And there you have it. I’ve put in the <a href="cham-extension-transcript.html">transcript</a> for the demo project I have in this blog post, as well as the <a href="../../posts/how-i-use-llms-to-write-code-in-2026/implementation-plan.html">implementation plan</a> it generated. Let me know if this works for you, or if you use different techniques that work for you.</p>



 ]]></description>
  <guid>https://blog.jean-francois.im/posts/how-i-use-llms-to-write-code-in-2026/</guid>
  <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>The Sensors Aren’t Alright</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/the-sensors-aren-t-alright/</link>
  <description><![CDATA[ 






<p>A few weeks ago, I automated my air purifiers in the house using Home Assistant, MQTT, and a Python controller to turn the air quality data from the sensors that I have into a speed control for the air purifiers.</p>
<p>One thing that I noticed is that the air purifiers in one part of the house tended to run faster than in a different part of the house. I originally assumed it was because the kitchen and dining areas had more particles in suspension due from the cooking, but that should have been handled by the air purifiers eventually.</p>
<p>I then decided to open one of the PMSA003 sensors to clean it up. It turns out that in practice, due to the design of the sensor, there isn’t really a whole lot to clean up inside and it was actually pretty clean, since the air flow is such that it passes through the sensing cavity, far from the laser diode and the photoreceptor.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Since I couldn’t find instructions anywhere on the internet as to how to open a PMSA003, you’ll first need to remove the two small screws holding the fan. Then, you can pry open the sheet metal lid and bottom by inserting a very thin flat heat screwdriver between the middle plastic lip that separates the upper and lower parts of the sensor’s sheet metal housing.</p>
<p>Running the screwdriver along the edge of the sheet metal will allow you to fold it outwards slightly, thus releasing that side from the clips in the plastic housing. Inside, there’s a small L shaped PCB, three springs (one of which is slightly longer than the others), and a few screws holding the whole thing together.</p>
<p>When putting back the sensor together, fold the sheet metal back into its normal unbent shape using needlenose pliers, then push it back into its regular position to close the sensor housing. You may need to tape the edges of the sensor housing to prevent air leaks, if the sheet metal isn’t bent back perfectly.</p>
</div></div><p>The only thing there is to clean is the little fan, which can be cleaned up with a soft bristle brush, but that’s also on the exit path so that doesn’t really leak particles into the sensing cavity. So after spending far too long on cleaning it up, the difference before and after is a sensor that performs exactly the same as before but looks markedly worse with sheet metal that’s bent slightly out of shape and tape to seal it off.</p>
<p>However, just claiming that there is no change without evidence make it a rather dubious unsubstantiated opinion of which the internet is full of. But since we have the ability to make statistical inference of questionable pertinence on the internet, let’s do so.</p>
<p>So the first question is whether or not the error rate on the sensors is different before and after opening it. If there are no differences between the sensors, we should have a similar error rate before and after:</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(tidyverse)</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(glue)</span>
<span id="cb1-3"></span>
<span id="cb1-4">day_before_yesterday <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span></span>
<span id="cb1-5">tibble<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tribble</span>(</span>
<span id="cb1-6">                  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>hour,    <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>macAddress, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>bad, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>total,           <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>pct_bad,</span>
<span id="cb1-7">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 00:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">87</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0289517470881864</span>,</span>
<span id="cb1-8">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 00:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">170</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0565912117177097</span>,</span>
<span id="cb1-9">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 01:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">101</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0336218375499334</span>,</span>
<span id="cb1-10">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 01:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">175</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0582362728785358</span>,</span>
<span id="cb1-11">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 02:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.031624500665779</span>,</span>
<span id="cb1-12">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 02:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">158</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0525965379494008</span>,</span>
<span id="cb1-13">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 03:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">89</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0296271637816245</span>,</span>
<span id="cb1-14">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 03:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">192</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0639147802929427</span>,</span>
<span id="cb1-15">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 04:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">101</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0336218375499334</span>,</span>
<span id="cb1-16">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 04:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">176</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0585885486018642</span>,</span>
<span id="cb1-17">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 05:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">109</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0362728785357737</span>,</span>
<span id="cb1-18">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 05:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">161</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.053595206391478</span>,</span>
<span id="cb1-19">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 06:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">105</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0349533954727031</span>,</span>
<span id="cb1-20">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 06:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0599201065246338</span>,</span>
<span id="cb1-21">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 07:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0329560585885486</span>,</span>
<span id="cb1-22">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 07:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">169</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0562396006655574</span>,</span>
<span id="cb1-23">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 08:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">97</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0322902796271638</span>,</span>
<span id="cb1-24">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 08:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">155</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0515978695073236</span>,</span>
<span id="cb1-25">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 09:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">89</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0296173044925125</span>,</span>
<span id="cb1-26">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 09:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">184</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0612516644474035</span>,</span>
<span id="cb1-27">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 10:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">109</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0362728785357737</span>,</span>
<span id="cb1-28">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 10:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">178</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.059234608985025</span>,</span>
<span id="cb1-29">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 11:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">83</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,   <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02762982689747</span>,</span>
<span id="cb1-30">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 11:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">173</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.057589880159787</span>,</span>
<span id="cb1-31">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 12:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">101</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0336218375499334</span>,</span>
<span id="cb1-32">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 12:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">205</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0682423435419441</span>,</span>
<span id="cb1-33">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 13:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.033288948069241</span>,</span>
<span id="cb1-34">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 13:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">191</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0635818908122503</span>,</span>
<span id="cb1-35">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 14:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0316139767054908</span>,</span>
<span id="cb1-36">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 14:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">165</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0549084858569052</span>,</span>
<span id="cb1-37">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 15:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">96</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0319573901464714</span>,</span>
<span id="cb1-38">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 15:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">151</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0502663115845539</span>,</span>
<span id="cb1-39">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 16:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">91</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0302929427430093</span>,</span>
<span id="cb1-40">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 16:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">172</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0572569906790945</span>,</span>
<span id="cb1-41">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 17:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0329450915141431</span>,</span>
<span id="cb1-42">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 17:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">172</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0572569906790945</span>,</span>
<span id="cb1-43">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 18:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">104</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0346205059920107</span>,</span>
<span id="cb1-44">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 18:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">167</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0555740432612313</span>,</span>
<span id="cb1-45">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 19:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">87</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0289613848202397</span>,</span>
<span id="cb1-46">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 19:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">204</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0679094540612517</span>,</span>
<span id="cb1-47">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 20:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">99</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2996</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0330440587449933</span>,</span>
<span id="cb1-48">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 20:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">206</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2997</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0687354020687354</span>,</span>
<span id="cb1-49">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 21:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.031624500665779</span>,</span>
<span id="cb1-50">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 21:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">216</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0719041278295606</span>,</span>
<span id="cb1-51">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 22:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.033288948069241</span>,</span>
<span id="cb1-52">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 22:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">204</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0679094540612517</span>,</span>
<span id="cb1-53">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 23:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">101</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0336106489184692</span>,</span>
<span id="cb1-54">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-28 23:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">204</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0679094540612517</span></span>
<span id="cb1-55">  )</span>
<span id="cb1-56"></span>
<span id="cb1-57">today <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> tibble<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tribble</span>(</span>
<span id="cb1-58">                  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>hour,    <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>macAddress, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>bad, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>total,           <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>pct_bad,</span>
<span id="cb1-59">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 00:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">87</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0289613848202397</span>,</span>
<span id="cb1-60">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 00:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">192</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0639147802929427</span>,</span>
<span id="cb1-61">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 01:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">91</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0302828618968386</span>,</span>
<span id="cb1-62">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 01:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">173</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.057589880159787</span>,</span>
<span id="cb1-63">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 02:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">102</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0339547270306258</span>,</span>
<span id="cb1-64">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 02:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">170</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0565912117177097</span>,</span>
<span id="cb1-65">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 03:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">94</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2988</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0314591700133869</span>,</span>
<span id="cb1-66">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 03:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">182</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2990</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0608695652173913</span>,</span>
<span id="cb1-67">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 04:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17</span>L,   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">498</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.034136546184739</span>,</span>
<span id="cb1-68">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 04:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">31</span>L,   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">497</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.062374245472837</span>,</span>
<span id="cb1-69">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 05:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">26</span>L,   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">895</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0290502793296089</span>,</span>
<span id="cb1-70">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 05:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>L,   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">895</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0782122905027933</span>,</span>
<span id="cb1-71">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 06:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">33</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1194</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0276381909547739</span>,</span>
<span id="cb1-72">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 06:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">193</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2987</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0646133244057583</span>,</span>
<span id="cb1-73">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 07:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">80</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2094</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0382043935052531</span>,</span>
<span id="cb1-74">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 07:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">51</span>L,   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">702</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0726495726495727</span>,</span>
<span id="cb1-75">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 08:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0316139767054908</span>,</span>
<span id="cb1-76">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 08:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">151</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2262</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0667550839964633</span>,</span>
<span id="cb1-77">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 09:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">92</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2926</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0314422419685578</span>,</span>
<span id="cb1-78">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 09:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">211</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2983</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0707341602413677</span>,</span>
<span id="cb1-79">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 10:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">98</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0326231691078562</span>,</span>
<span id="cb1-80">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 10:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">199</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0662229617304493</span>,</span>
<span id="cb1-81">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 11:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">98</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L,   <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.03261231281198</span>,</span>
<span id="cb1-82">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 11:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">218</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0725457570715474</span>,</span>
<span id="cb1-83">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 12:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">93</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0309587217043941</span>,</span>
<span id="cb1-84">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 12:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">217</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.072237017310253</span>,</span>
<span id="cb1-85">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 13:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">91</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0302828618968386</span>,</span>
<span id="cb1-86">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 13:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">210</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0699067909454061</span>,</span>
<span id="cb1-87">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 14:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">87</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0289613848202397</span>,</span>
<span id="cb1-88">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 14:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">214</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0712383488681758</span>,</span>
<span id="cb1-89">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 15:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">97</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0322902796271638</span>,</span>
<span id="cb1-90">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 15:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">211</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0702396804260985</span>,</span>
<span id="cb1-91">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 16:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">92</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0306258322237017</span>,</span>
<span id="cb1-92">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 16:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">223</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0742096505823627</span>,</span>
<span id="cb1-93">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 17:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.033288948069241</span>,</span>
<span id="cb1-94">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 17:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">205</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0682423435419441</span>,</span>
<span id="cb1-95">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 18:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">74</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2182</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0339138405132906</span>,</span>
<span id="cb1-96">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 18:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">427</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2982</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.143192488262911</span>,</span>
<span id="cb1-97">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 19:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.033288948069241</span>,</span>
<span id="cb1-98">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 19:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">139</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3004</span>L,  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.046271637816245</span>,</span>
<span id="cb1-99">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 20:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">86</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2877</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0298922488703511</span>,</span>
<span id="cb1-100">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 20:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">218</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2860</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0762237762237762</span>,</span>
<span id="cb1-101">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 21:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">95</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2982</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0318578135479544</span>,</span>
<span id="cb1-102">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 21:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">166</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2982</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0556673373574782</span>,</span>
<span id="cb1-103">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 22:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">105</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0349417637271215</span>,</span>
<span id="cb1-104">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 22:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">220</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3005</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0732113144758735</span>,</span>
<span id="cb1-105">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 23:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">98</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2968</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0330188679245283</span>,</span>
<span id="cb1-106">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2026-04-30 23:00:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">170</span>L,  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2877</span>L, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0590893291623219</span></span>
<span id="cb1-107">  )</span>
<span id="cb1-108"></span>
<span id="cb1-109"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (sensor <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dc4f2260c2a7"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"840d8ea81a38"</span>)) {</span>
<span id="cb1-110">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span>(glue<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glue</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sensor {sensor} error rate different?"</span>))</span>
<span id="cb1-111">  today_sensor <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> today <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(macAddress <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> sensor)</span>
<span id="cb1-112">  day_before_yesterday_sensor <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> day_before_yesterday <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(macAddress <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> sensor)</span>
<span id="cb1-113">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">prop.test</span>(</span>
<span id="cb1-114">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(day_before_yesterday_sensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>bad), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(today_sensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>bad)),</span>
<span id="cb1-115">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">n=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(day_before_yesterday_sensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>total), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(today_sensor<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>total))</span>
<span id="cb1-116">  ))</span>
<span id="cb1-117">}</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>Sensor dc4f2260c2a7 error rate different?

    2-sample test for equality of proportions with continuity correction

data:  c(sum(day_before_yesterday_sensor$bad), sum(today_sensor$bad)) out of c(sum(day_before_yesterday_sensor$total), sum(today_sensor$total))
X-squared = 51.523, df = 1, p-value = 7.076e-13
alternative hypothesis: two.sided
95 percent confidence interval:
 -0.012230667 -0.006951284
sample estimates:
    prop 1     prop 2 
0.06003274 0.06962371 

Sensor 840d8ea81a38 error rate different?

    2-sample test for equality of proportions with continuity correction

data:  c(sum(day_before_yesterday_sensor$bad), sum(today_sensor$bad)) out of c(sum(day_before_yesterday_sensor$total), sum(today_sensor$total))
X-squared = 0.20103, df = 1, p-value = 0.6539
alternative hypothesis: two.sided
95 percent confidence interval:
 -0.001449097  0.002338800
sample estimates:
    prop 1     prop 2 
0.03234621 0.03190136 </code></pre>
</div>
</div>
<p>And what do you know, it’s actually worse off, going from 6% to 7% of readings from the cleaned sensor being marked as erroneous, so if anything, opening it was a <em>bad idea</em>.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Technically it could be another reason like ESD discharge during the disassembly process, a poorly reconnected sensor or disturbance of the board, or simply an unfortunate coincidence of a cheap AliExpress sensor.</p>
</div></div><p>Ignoring the error rate, we can also look at the actual measurements from the sensor. I placed the two sensors as close as possible, approximately 3mm apart, and got Claude to analyze the CSV data from the sensor and generate a line plot of the different measurements from the sensors, broken down by sensor MAC address, faceted by measurement, with two relevant events added to the timeline — cooking lunch and switching both sensors to use a single 9V power source — to see if they had any effects.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/particle-measurements-fs8.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Sensor measurement plot"><img src="https://blog.jean-francois.im/posts/the-sensors-aren-t-alright/images/particle-measurements-fs8.png" class="img-fluid figure-img" alt="Sensor measurement plot"></a></p>
<figcaption>Sensor measurement plot</figcaption>
</figure>
</div>
<p>What the graph shows is that the other sensor consistently undercounts particles, especially at the 1μm size, that it isn’t just a lower sensitivity at low particle counts, nor a power issue despite the 5V rail not looking so hot, since both sensors basically see the same voltage at the PMSA003 Vin pin.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/scope.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="5V rail voltage"><img src="https://blog.jean-francois.im/posts/the-sensors-aren-t-alright/images/scope.jpg" class="img-fluid figure-img" alt="5V rail voltage"></a></p>
<figcaption>5V rail voltage</figcaption>
</figure>
</div>
<p>My hypothesis is that the fan is actually starting to fail, hence the higher reading failure rate. When disassembling the particle sensor, I noticed that the fan connector appears to have three pins, indicating that the fan speed could be measured by the sensor. If that’s the case and the fan is slowing down below nominal speed and thus drawing in less air through the sensor cavity, that would align with the lower readings, especially at larger particle sizes.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Honestly for an AliExpress sensor that’s probably a factory reject that fell out the back of a truck, it worked out pretty well, and I can just get a new one for twenty something bucks. Chances thrown, nothing’s free.</p>
</div></div><p>So I guess that sensor is kind of shot and needs a replacement, unfortunately.</p>
<p>On the upside, that had me researching what the current air quality sensors are, and I noticed that Sensirion has come up with their SEN66 combo sensor that has a laser particle sensor, a CO₂ sensor that’s likely the photoacoustic NDIR SCD43, relative humidity and temperature sensors, metal oxide sensors to estimate VOC and NOx<sup>1</sup>, and a fan to circulate air over all of those sensors. Considering that basically measures almost everything you’d want to measure for indoors air quality<sup>2</sup> and it speaks I²C, that’s pretty much a slam dunk as far as indoors air quality measurement is concerned. Considering a SCD43 is about twenty bucks, a comparable SPS30 is about thirty bucks, getting the whole package in the SEN66 for less than sixty bucks is pretty decent.</p>
<p>So it looks like I’ll be retiring those sensors that are grown up but whose lives are worn, and look towards a new future that’s so bright with the new SEN66.</p>




<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>The VOC and NOx values are estimates rather than true values, but that’s common to all inexpensive MOX sensors. In practice, the important part is the directionality of the estimate, not the actual absolute value, since if it’s high you’d open the window whether the concentration of VOCs is 300ppb or 538.53ppb.↩︎</p></li>
<li id="fn2"><p>Maybe other than formaldehyde, radon, carbon monoxide, and ozone.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Projects</category>
  <category>Electronics</category>
  <category>Air Quality</category>
  <guid>https://blog.jean-francois.im/posts/the-sensors-aren-t-alright/</guid>
  <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://blog.jean-francois.im/posts/the-sensors-aren-t-alright/images/post-thumbnail.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Generalizing TOTP Anagram Counting for Different Bases</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/generalizing-totp-anagram-counting-for-different-bases/</link>
  <description><![CDATA[ 






<p>While working on the submission for <a href="https://oeis.org/A394980">A394980</a> — the count of anagram pairs in halves of TOTP codes — one of my more mathematically inclined friends asked if this was just a different series in disguise. I couldn’t find any other matching sequences initially, but when working on it, the other dimension that I didn’t explore initially was the assumption that one is working with base 10, as would be the case for regular TOTP codes. Changing the base to different values is where the plot thickens.</p>
<p>Changing the code to use different values for the base is relatively trivial, so for bases 2 to 10, the first 25 terms are:</p>
<div id="f87ded2a" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> itertools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> product, combinations_with_replacement</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> factorial, perm</span>
<span id="cb1-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> distinct_perms(seq):</span>
<span id="cb1-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct permutations of seq."""</span></span>
<span id="cb1-8">    counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(seq)</span>
<span id="cb1-9">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.values():</span>
<span id="cb1-11">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(k)</span>
<span id="cb1-12">    val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factorial(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb1-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> val</span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> partitions(n, max_part<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb1-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Generate all integer partitions of n."""</span></span>
<span id="cb1-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> max_part <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb1-18">        max_part <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n</span>
<span id="cb1-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb1-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> []</span>
<span id="cb1-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span>
<span id="cb1-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> first <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(n, max_part), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rest <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> partitions(n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> first, first):</span>
<span id="cb1-24">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> [first] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rest</span>
<span id="cb1-25"></span>
<span id="cb1-26"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> partition_freq(p, base<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>):</span>
<span id="cb1-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct prefixes of length n with the shape described by partition p."""</span></span>
<span id="cb1-28">    num <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> perm(base, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p))</span>
<span id="cb1-29">    group_sizes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(p).values()</span>
<span id="cb1-30">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-31">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> group_sizes:</span>
<span id="cb1-32">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(g)</span>
<span id="cb1-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> num <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb1-34"></span>
<span id="cb1-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> expand(p):</span>
<span id="cb1-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Turns a partition into a sequence, eg. [4,1,1] -&gt; [1,1,1,1,2,3]"""</span></span>
<span id="cb1-37">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb1-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, count <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(p):</span>
<span id="cb1-39">        result.extend([i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> count)</span>
<span id="cb1-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb1-41"></span>
<span id="cb1-42"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> base <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">11</span>):</span>
<span id="cb1-43">    seq <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(partition_freq(p, base) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (distinct_perms(expand(p))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> partitions(n)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">26</span>)]</span>
<span id="cb1-44">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'b=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>base<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>join(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, seq))<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>b=2: 2,6,20,70,252,924,3432,12870,48620,184756,705432,2704156,10400600,40116600,155117520,601080390,2333606220,9075135300,35345263800,137846528820,538257874440,2104098963720,8233430727600,32247603683100,126410606437752
b=3: 3,15,93,639,4653,35169,272835,2157759,17319837,140668065,1153462995,9533639025,79326566595,663835030335,5582724468093,47152425626559,399769750195965,3400775573443089,29016970072920387,248256043372999089,2129158861755559587,18301184259810988815,157626238006835196237,1360135024740956026161,11756409817108040588403
b=4: 4,28,256,2716,31504,387136,4951552,65218204,878536624,12046924528,167595457792,2359613230144,33557651538688,481365424895488,6956365106016256,101181938814289564,1480129751586116848,21761706991570726096,321401321741959062016,4766118425002290943216,70937237822612227230784,1059325264441498545599488,15867288561475917552050176,238332503119317701296868416,3588998891197749315801781504
b=5: 5,45,545,7885,127905,2241225,41467725,798562125,15855173825,322466645545,6687295253325,140927922498025,3010302779775725,65046639827565525,1419565970145097545,31249959913055650125,693192670456484513025,15480814434909059226825,347819791175860063294125,7857219150562637891693385,178365050610159455909952525,4067061957851359126795514325,93113307750242799253647686025,2139705756334600522205487928425,49337502810931248099790152915405
b=6: 6,66,996,18306,384156,8848236,218040696,5651108226,152254667436,4229523740916,120430899525096,3499628148747756,103446306284890536,3102500089343886696,94219208840385966096,2892652835496484004226,89662253086458906345036,2802887038905690746642916,88285112311736445352115976,2799753247995266147347843956,89333847417312523954034046936,2866354076060681446555954224696,92437346938232317505833680390576,2994900985489170290757597878329836,97447591912171214828256883106131656
b=7: 7,91,1645,36715,948157,27210169,844691407,27840317995,961404929485,34453695086041,1272342313935007,48163840784596201,1861408648486464175,73216844934326184595,2923876764438202454245,118311576139002457750315,4843019129955904050049645,200285107637533495666136425,8358772615816778247959630575,351714866444503250286117523465,14908928917141407766153378543855,636226231030579337275522117546915,27316683312186657375742935536430325,1179425650762021100313399983695203625,51184907683955175375444981664073129407
b=8: 8,120,2528,66424,2039808,70283424,2643158400,106391894904,4518833256512,200396211454720,9205443151733760,435368682010660000,21100379936684418560,1044115187294444772480,52597451834668445910528,2691037806733052553149304,139567074682665782246950080,7326035505602088571550898624,388692676307093660214513983232,20821560514105053341045830189824,1125071052804606862976953994944512,61270967628785336626069622639938560,3360729604535080838933867116477304832,185546031656019274031650908498763590816,10305688197366722917347399525269829719808
b=9: 9,153,3681,111321,3965409,159700401,7071121017,337386574809,17091486402849,909016606157553,50322658889918457,2880377856928886769,169569570177502573113,10224662030381995585353,629363959548197255942481,39439533486823544576759769,2510598661017963812643872673,162048024871184932602477539121,10589311049996692605851511195321,699665020890634054314125660348721,46691291797797628279076275449673209,3144108851688963610024995327698430153,213463410619979489585755003304528851281,14601839580495416551702129260310203023601,1005731803116618747973879291182068848700409
b=10: 10,190,5140,175870,7132060,329009500,16786131400,928136093950,54775566167500,3410496772665940,222005754890212600,15000987483726651100,1046188137708903907000,74962723424363171666200,5498130019391836779330640,411530535654245301470621950,31356088574298606320386653100,2427021041017043491153965921700,190503335542483372330410996741400,15141611551571136170888372040881620,1217139074924843939858814351399207400,98842200132473370747145638230967749800,8101728898006244345372661851453503553200,669726723998568836522230181105574128110300,55795274602503578824222289822443655978969560</code></pre>
</div>
</div>
<p>Interestingly, some of these sequences do appear to exist in the OEIS! Specifically, as of the time of writing this blog post, these ones match the first terms generated:</p>
<dl>
<dt>b=2</dt>
<dd>
<a href="https://oeis.org/A000984">A000984</a> Central binomial coefficients: <img src="https://latex.codecogs.com/png.latex?%5Cbinom%7B2n%7D%7Bn%7D%20=%20%5Cfrac%7B(2n)!%7D%7B(n!)%5E2%7D">
</dd>
<dt>b=3</dt>
<dd>
<a href="https://oeis.org/A002893">A002893</a> <img src="https://latex.codecogs.com/png.latex?a(n)%20=%20%5Csum_%7Bk=0%7D%5E%7Bn%7D%20%5Cbinom%7Bn%7D%7Bk%7D%5E2%20%5Cbinom%7B2k%7D%7Bk%7D">
</dd>
<dt>b=4</dt>
<dd>
<a href="https://oeis.org/A002895">A002895</a> Domb numbers: number of 2n-step polygons on diamond lattice. <img src="https://latex.codecogs.com/png.latex?a(n)%20=%20%5Csum_%7Bk=0%7D%5E%7Bn%7D%20%5Cbinom%7Bn%7D%7Bk%7D%5E2%20%5Cbinom%7B2k%7D%7Bk%7D%20%5Cbinom%7B2(n-k)%7D%7Bn-k%7D">
</dd>
<dt>b=5</dt>
<dd>
<a href="https://oeis.org/A169714">A169714</a> The function <img src="https://latex.codecogs.com/png.latex?W_5(2n)%20=%20%5Cint_0%5E1%20%5Ccdots%20%5Cint_0%5E1%20%5Cleft(%5Csum_%7Bi=1%7D%5E%7B5%7D%20%5Ccos(2%5Cpi%20x_i)%5Cright)%5E%7B2n%7D%20dx_1%20%5Ccdots%20dx_5">
</dd>
<dt>b=6</dt>
<dd>
<a href="https://oeis.org/A169715">A169715</a> The function <img src="https://latex.codecogs.com/png.latex?W_6(2n)%20=%20%5Cint_0%5E1%20%5Ccdots%20%5Cint_0%5E1%20%5Cleft(%5Csum_%7Bi=1%7D%5E%7B6%7D%20%5Ccos(2%5Cpi%20x_i)%5Cright)%5E%7B2n%7D%20dx_1%20%5Ccdots%20dx_6">
</dd>
<dt>b=8</dt>
<dd>
<a href="https://oeis.org/A385286">A385286</a> <img src="https://latex.codecogs.com/png.latex?a(n)%20=%20(n!)%5E2%20%5Bx%5En%5D%5C,%20%7B%7D_0F_0%5C!%5Cleft(%5Cleft%5B%5C,%5Cright%5D;%20%5Cleft%5B1%5Cright%5D;%20x%5Cright)%5E8">
</dd>
</dl>
<p>Unfortunately, it’s beyond my current mathematical ability to prove that they’re equivalent, but there are a few things that I can mention.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?b=5"> and <img src="https://latex.codecogs.com/png.latex?b=6">, Proposition 1 on page 6 of Borwein <em>et al</em> (2011)<sup>1</sup> mentions the definition of <img src="https://latex.codecogs.com/png.latex?W_n(2k)"></p>
<p><img src="https://latex.codecogs.com/png.latex?W_n(2k)%20=%20%5Csum_%7Ba_1+%5Ccdots+a_n=k%7D%20%5Cbinom%7Bk%7D%7Ba_1,%5Cldots,a_n%7D%5E2"></p>
<p>which on page 7, they mention is also the exact same formula as the definition of the combinatorial sums of multinomial coefficients squared</p>
<p><img src="https://latex.codecogs.com/png.latex?f_n(k)%20=%20%5Csum_%7Ba_1+%5Ccdots+a_n=k%7D%20%5Cbinom%7Bk%7D%7Ba_1,%5Cldots,a_n%7D%5E2"></p>
<p>Those sums are discussed in Richmond and Shallit (2008)<sup>2</sup> in which they mention that <img src="https://latex.codecogs.com/png.latex?f_n(k)"> counts abelian squares — strings <img src="https://latex.codecogs.com/png.latex?xx'"> of length <img src="https://latex.codecogs.com/png.latex?2k"> over an alphabet with <img src="https://latex.codecogs.com/png.latex?n"> letters such that <img src="https://latex.codecogs.com/png.latex?x'"> is a permutation of <img src="https://latex.codecogs.com/png.latex?x"> — and that <img src="https://latex.codecogs.com/png.latex?f_2(n)"> matches sequence A000984, <img src="https://latex.codecogs.com/png.latex?f_3(n)"> matches sequence A002893, <img src="https://latex.codecogs.com/png.latex?f_4(n)"> matches sequence A002895.</p>
<p>Given that my TOTP problem matches with the definition of what <img src="https://latex.codecogs.com/png.latex?f_%7Bn%7D(k)"> would be in Richmond and Shallit for a string of length <img src="https://latex.codecogs.com/png.latex?2k"> and an alphabet of <img src="https://latex.codecogs.com/png.latex?n"> letters, we can then rewrite that the <img src="https://latex.codecogs.com/png.latex?k">th term of my sequence is <img src="https://latex.codecogs.com/png.latex?a_%7Bb%7D(n)=f_b(k)">. This allows us to conclude that:</p>
<dl>
<dt>b=2</dt>
<dd>
<img src="https://latex.codecogs.com/png.latex?a_%7B2%7D(n)=f_2(k)">, which Richmond and Shallit mention is <a href="https://oeis.org/A000984">A000984</a>
</dd>
<dt>b=3</dt>
<dd>
<img src="https://latex.codecogs.com/png.latex?a_%7B3%7D(n)=f_3(k)">, which Richmond and Shallit mention is <a href="https://oeis.org/A002893">A002893</a>
</dd>
<dt>b=4</dt>
<dd>
<img src="https://latex.codecogs.com/png.latex?a_%7B4%7D(n)=f_4(k)">, which Richmond and Shallit mention is <a href="https://oeis.org/A002895">A002895</a>
</dd>
<dt>b=5</dt>
<dd>
<img src="https://latex.codecogs.com/png.latex?a_%7B5%7D(n)=f_5(k)">, since <img src="https://latex.codecogs.com/png.latex?f_5(k)=W_5(2k)"> by definition, this is <a href="https://oeis.org/A169714">A169714</a>
</dd>
<dt>b=6</dt>
<dd>
<img src="https://latex.codecogs.com/png.latex?a_%7B6%7D(n)=f_6(k)">, since <img src="https://latex.codecogs.com/png.latex?f_6(k)=W_6(2k)"> by definition, this is <a href="https://oeis.org/A169715">A169715</a>
</dd>
</dl>
<p>This leaves us with the case of <img src="https://latex.codecogs.com/png.latex?b=8">. This appears to match <a href="https://oeis.org/A385286">A385286</a>, and in their definition, they mention that</p>
<blockquote class="blockquote">
<p>We regard this sequence in the list of sequences n -&gt; A287316(n, 2^k) for k = 3.</p>
</blockquote>
<p>and they cross-reference</p>
<blockquote class="blockquote">
<p>A000984 (k=1), A002895 (k=2), this sequence (k=3), A287316</p>
</blockquote>
<p>This would suggest that the pattern is <img src="https://latex.codecogs.com/png.latex?f_%7B2%5Ek%7D">, so for A000984 <img src="https://latex.codecogs.com/png.latex?f_2=f_%7B2%5E1%7D">, and for A002895 <img src="https://latex.codecogs.com/png.latex?f_4=f_%7B2%5E2%7D">, meaning that A385286 is effectively <img src="https://latex.codecogs.com/png.latex?f_%7B2%5E3%7D"> in disguise and thus the same as this sequence for <img src="https://latex.codecogs.com/png.latex?b=8">.</p>
<p>Of note is the fact that <img src="https://latex.codecogs.com/png.latex?b%20%5Cin%20%5C%7B7,9%5C%7D"> aren’t in the OEIS, which is probably an interesting artifact of nobody ever needing them or studying them in a way that would lead them to add them to the OEIS, rather than them being completely novel. Given that Richmond and Shallit only cover <img src="https://latex.codecogs.com/png.latex?f_n"> for <img src="https://latex.codecogs.com/png.latex?n%20%5Cleq%0A6">, and <img src="https://latex.codecogs.com/png.latex?f_8"> is there due to <img src="https://latex.codecogs.com/png.latex?2%5Ek"> for <img src="https://latex.codecogs.com/png.latex?k=3">, that would explain the lack of <img src="https://latex.codecogs.com/png.latex?f_7"> and <img src="https://latex.codecogs.com/png.latex?f_9"> in the OEIS.</p>
<p>Still, for a random shower thought about TOTP codes, it was quite an interesting mathematical rabbit hole to fall into.</p>




<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>Borwein, J. M., Nuyens, D., Straub, A., &amp; Wan, J. (2011). Some arithmetic properties of short random walk integrals. <em>The Ramanujan Journal</em>, <em>26</em>(1), 109–132. <a href="https://doi.org/10.1007/s11139-011-9325-y">doi:10.1007/s11139-011-9325-y</a>↩︎</p></li>
<li id="fn2"><p>Richmond, L. B., &amp; Shallit, J. (2008). Counting Abelian squares. <em>The Electronic Journal of Combinatorics</em>, <em>16</em>. <a href="https://arxiv.org/abs/0807.5028">arXiv:0807.5028</a>↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>Math</category>
  <category>Anagrams</category>
  <category>Combinatorics</category>
  <category>TOTP</category>
  <category>OEIS</category>
  <guid>https://blog.jean-francois.im/posts/generalizing-totp-anagram-counting-for-different-bases/</guid>
  <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Palindromes and Anagrams in TOTP Codes</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/palindromes-and-anagrams-in-totp-codes/</link>
  <description><![CDATA[ 






<p>This morning, I entered yet another of those six digit TOTP codes to log into an online service. One thing that I always find interesting is when the second half of the code is similar to the first half, like say <code>675567</code>. My curiosity got the best of me and I just had to know how often that happens.</p>
<p>The interesting cases would be a palindrome (eg. <code>123321</code>), a repetition (eg. <code>123123</code>), and an anagram of the first digits (eg. <code>123213</code>, <code>123312</code>, etc.).</p>
<p>The first case, palindromes, is pretty easy. For a six digit base ten number that’s zero padded, any three digit prefix has exactly 1000 possible three digit suffixes, and only one of them is the reverse of the prefix, thus for all possible six digit codes with a given three digit prefix, <img src="https://latex.codecogs.com/png.latex?1/1000"> is a palindrome. Since there are 1000 such prefixes, and <img src="https://latex.codecogs.com/png.latex?10%5E6"> possible six digit codes, the probability of getting a TOTP code that’s a palindrome is <img src="https://latex.codecogs.com/png.latex?1000/1000000%20=%201/1000">.</p>
<p>The second case is a repetition, which has effectively the same number of occurrences as palindromes, so the probability of getting a TOTP code like 123123 with both halves identical is <img src="https://latex.codecogs.com/png.latex?1/1000">.</p>
<p>For those, we can actually generalize that for a code of length <img src="https://latex.codecogs.com/png.latex?2n">, the probability of it being a palindrome is <img src="https://latex.codecogs.com/png.latex?1/10%5En">, for which the proof is relatively trivial. Given that there are <img src="https://latex.codecogs.com/png.latex?10%5En"> prefixes, each prefix has exactly one valid suffix to make a palindrome, and <img src="https://latex.codecogs.com/png.latex?10%5E%7B2n%7D"> total numbers, we get a probability of <img src="https://latex.codecogs.com/png.latex?10%5En%20/%2010%5E%7B2n%7D"> which simplifies to <img src="https://latex.codecogs.com/png.latex?1/(10%5En)">.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>In other words, for a code of length 6 like a regular TOTP token, <img src="https://latex.codecogs.com/png.latex?n"> would be 3. The length being <img src="https://latex.codecogs.com/png.latex?2n"> makes it an even length, which is necessary in order to be able to split the code into two halves of equal length.</p>
</div></div><p>That leaves the case of the anagrams, which are permutations of the first half of the TOTP code.</p>
<p>Since that’s a bit more complicated, what we would want to do is count the number of such permutations, which would allow us to calculate the probability of them given a code of length <img src="https://latex.codecogs.com/png.latex?2n"> for any arbitrary <img src="https://latex.codecogs.com/png.latex?n">.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?n=1">, it’s pretty easy to prove that there are ten such permutations, since the first half has to equal the second, and there are ten single digits since we’re using base ten, which gives us <img src="https://latex.codecogs.com/png.latex?1%20%5Ctimes%2010"></p>
<p>For <img src="https://latex.codecogs.com/png.latex?n=2">, it’s a bit more complicated, since doubled digits only have one valid permutation (eg. <code>1111</code>), but different digits have two valid permutations (eg. <code>1212</code> and <code>1221</code>). Since we know that there are ten valid doubled digits, and <img src="https://latex.codecogs.com/png.latex?10%5E2"> prefixes, we can enumerate each case:</p>
<ul>
<li>one valid permutation for each prefix that’s doubled digits, of which there are ten</li>
<li>two valid permutations for each prefix that doesn’t have doubled digits, of which there are <img src="https://latex.codecogs.com/png.latex?10%5E2-10"></li>
</ul>
<p>As such, we get <img src="https://latex.codecogs.com/png.latex?1%20%5Ctimes%2010%20+%202%20%5Ctimes%20(10%5E2-10)%20=%20190">.</p>
<p>For <img src="https://latex.codecogs.com/png.latex?n=3">, we get different cases:</p>
<ul>
<li>one distinct permutation for each prefix that are three identical digits (or one distinct digit), of which there are ten</li>
<li>three distinct permutations for each possible prefix with two distinct digits, of which there are <img src="https://latex.codecogs.com/png.latex?270"> (<img src="https://latex.codecogs.com/png.latex?10%20%5Ctimes%209%20%5Ctimes%203">, since there are three positions where the doubled number can be)</li>
<li>six distinct permutations for each possible prefix with three distinct digits, of which there are <img src="https://latex.codecogs.com/png.latex?720"> (<img src="https://latex.codecogs.com/png.latex?10%20%5Ctimes%209%20%5Ctimes%208">)</li>
</ul>
<p>This gives us <img src="https://latex.codecogs.com/png.latex?1%20%5Ctimes%2010%20+%203%20%5Ctimes%20270%20+%206%20%5Ctimes%20720%20=%205140">.</p>
<p>Interestingly, at this point, we can start to see a pattern. For each <img src="https://latex.codecogs.com/png.latex?n"> we have <img src="https://latex.codecogs.com/png.latex?n"> different cases, each of which is the product of the number of distinct permutations of a prefix of length <img src="https://latex.codecogs.com/png.latex?n"> and the number of such prefixes with <img src="https://latex.codecogs.com/png.latex?m"> distinct digits.</p>
<p>However, if we try to expand the cases for <img src="https://latex.codecogs.com/png.latex?n=4">, the pattern starts becoming more complicated. In the case of <img src="https://latex.codecogs.com/png.latex?n=3,m=2">, there’s exactly one possible pattern <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,a,b%5C%7D"> — in other words, <img src="https://latex.codecogs.com/png.latex?a"> appearing twice and <img src="https://latex.codecogs.com/png.latex?b"> appearing once. But for <img src="https://latex.codecogs.com/png.latex?n=4,%20m=2"> means that we can get two subcases <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,a,a,b%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,a,b,b%5C%7D">, each with different numbers of permutations.</p>
<p>Unfortunately, this is the point where I hit the limits of my combinatorics toolkit, which isn’t sufficient to continue to expand this in order to get to a closed form.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>Even discussing it with Claude unfortunately only gets me to a discussion about Stirling numbers, for which I’d probably need an actual combinatorics class to understand.</p>
</div></div><p>What I do however have is the ability to write Python. A brute force approach is to simply count the number of items where the sorted prefix matches the sorted suffix by exhaustively enumerating them. A first stab at it would be something like this:</p>
<div id="a69abb08" class="cell" data-execution_count="1">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> count_prefix(n, prefix):</span>
<span id="cb1-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Count the number of suffixes of length n matching a prefix of length n."""</span></span>
<span id="cb1-5">    count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-6"></span>
<span id="cb1-7">    first <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>prefix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>{n}d<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>)</span>
<span id="cb1-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> suffix <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>n):</span>
<span id="cb1-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> first <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>suffix<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>{n}d<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'</span>):</span>
<span id="cb1-10">            count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-11">    </span>
<span id="cb1-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> count</span>
<span id="cb1-13"></span>
<span id="cb1-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> count_for_n(n):</span>
<span id="cb1-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Count the number of suffixes of length n matching a prefixe of length n, for all prefixes of length n."""</span></span>
<span id="cb1-16">    count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb1-17">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># We optimize this by only computing the prefixes that start with 0, then</span></span>
<span id="cb1-18">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># multiplying the resulting count by 10, hence the n - 1 and count * 10</span></span>
<span id="cb1-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> prefix <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>(n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)):</span>
<span id="cb1-20">        count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> count_prefix(n, prefix)</span>
<span id="cb1-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span></span>
<span id="cb1-22"></span>
<span id="cb1-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>]:</span>
<span id="cb1-24">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb1-25">    count <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> count_for_n(n)</span>
<span id="cb1-26">    elapsed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb1-27">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'n=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>count<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>elapsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s)'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>n=1 10 (0.0s)
n=2 190 (0.0s)
n=3 5140 (0.1s)
n=4 175870 (6.7s)</code></pre>
</div>
</div>
<p>That’s pretty inefficient, but we can completely ignore the fact that doing everything as string operations doesn’t make sense and just brute force it by using more cores:</p>
<pre><code>$ python3 test2.py
Using 32 cores
n=1 10 (0.0s)
n=2 190 (0.1s)
n=3 5140 (0.1s)
n=4 175870 (1.2s)
n=5 7132060 (109.5s)
n=6 329009500 (11411.3s)</code></pre>
<p>Obviously, that’s not going to be very useful for <img src="https://latex.codecogs.com/png.latex?n%20%3E%206">. A smarter approach would be to actually look at all the prefixes, and for each, we can look at the number of distinct permutations of each prefix.</p>
<p>Given a prefix, we can calculate the number of distinct permutations by calculating the number of total permutations and dividing by the number of permutations that are equivalent. For example, given <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,%20b,%20b,%20c,%20c,%20d,%20d,%20d,%0Ad%5C%7D">, there is <img src="https://latex.codecogs.com/png.latex?a%20%5Ctimes%201,%20b%20%5Ctimes%202,%20c%20%5Ctimes%202,%20d%20%5Ctimes%204">, which gives us <img src="https://latex.codecogs.com/png.latex?9!"> total permutations, and <img src="https://latex.codecogs.com/png.latex?1!%20%5Ctimes%202!%20%5Ctimes%202!%20%5Ctimes%204!"> permutations that are equivalent since they would swap equivalent letters, so <img src="https://latex.codecogs.com/png.latex?9!%20/%20(1!%0A%5Ctimes%202!%20%5Ctimes%202!%20%5Ctimes%204!)"> distinct permutations.</p>
<p>Since we can iterate over the possible prefixes of length <img src="https://latex.codecogs.com/png.latex?n"> using <code>product(range(10), repeat=n)</code> in Python, we can sum the number of distinct permutations for each prefix:</p>
<div id="2492f226" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> itertools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> product</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> factorial</span>
<span id="cb4-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb4-5"></span>
<span id="cb4-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> distinct_perms(seq):</span>
<span id="cb4-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct permutations of seq."""</span></span>
<span id="cb4-8">    counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(seq)</span>
<span id="cb4-9">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.values():</span>
<span id="cb4-11">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(k)</span>
<span id="cb4-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> factorial(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb4-13"></span>
<span id="cb4-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>):</span>
<span id="cb4-15">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb4-16">    total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(distinct_perms(seq) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> seq <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> product(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>), repeat<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>n))</span>
<span id="cb4-17">    elapsed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb4-18">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'n=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>elapsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s)'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>n=1 10 (0.0s)
n=2 190 (0.0s)
n=3 5140 (0.0s)
n=4 175870 (0.0s)
n=5 7132060 (0.2s)
n=6 329009500 (1.7s)</code></pre>
</div>
</div>
<p>That’s slightly faster, but can we do better?</p>
<p>It turns out that we can. In practice, we don’t really need to look at all possible prefixes. We just need to look at the distinct prefixes, and for each distinct prefix, count how often it would appear as the prefix of all possible permutations and multiply it with the number of distinct suffixes for that prefix.</p>
<p>But the interesting thing is that any distinct prefix permutation occurs just as often as a distinct suffix permutation, therefore for a given unique prefix that occurs <img src="https://latex.codecogs.com/png.latex?k"> times, the combination of prefix and suffix occurs <img src="https://latex.codecogs.com/png.latex?k%5E2"> times.</p>
<p>Given that we can iterate over the number of distinct prefixes using <code>combinations_with_replacement</code> from <code>itertools</code>, we get:</p>
<div id="17a1390c" class="cell" data-execution_count="3">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> itertools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> product, combinations_with_replacement</span>
<span id="cb6-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="cb6-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> factorial</span>
<span id="cb6-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb6-5"></span>
<span id="cb6-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> distinct_perms(seq):</span>
<span id="cb6-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct permutations of seq."""</span></span>
<span id="cb6-8">    counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(seq)</span>
<span id="cb6-9">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.values():</span>
<span id="cb6-11">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(k)</span>
<span id="cb6-12">    val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factorial(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb6-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> val</span>
<span id="cb6-14"></span>
<span id="cb6-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>):</span>
<span id="cb6-16">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb6-17">    total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(distinct_perms(seq) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> seq <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> combinations_with_replacement(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>), n))</span>
<span id="cb6-18">    elapsed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb6-19">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'n=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>elapsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s)'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>n=1 10 (0.0s)
n=2 190 (0.0s)
n=3 5140 (0.0s)
n=4 175870 (0.0s)
n=5 7132060 (0.0s)
n=6 329009500 (0.0s)
n=7 16786131400 (0.0s)
n=8 928136093950 (0.0s)
n=9 54775566167500 (0.1s)
n=10 3410496772665940 (0.2s)
n=11 222005754890212600 (0.4s)
n=12 15000987483726651100 (0.7s)
n=13 1046188137708903907000 (1.1s)
n=14 74962723424363171666200 (1.9s)</code></pre>
</div>
</div>
<p>Faster, but what if we don’t need to look at prefixes at all? After all, <img src="https://latex.codecogs.com/png.latex?%5C%7B1,1,2,2%5C%7D"> and <img src="https://latex.codecogs.com/png.latex?%5C%7B2,2,3,3%5C%7D"> are functionally the same shape, they’re both <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,a,b,b%5C%7D">.</p>
<p>Ideally, we’d want to iterate over all possible shapes, determine how frequently they happen, and then multiply that by the number of the distinct permutations for both the prefix and suffix.</p>
<p>Thinking about this, all of the shapes correspond to integer partitions of <img src="https://latex.codecogs.com/png.latex?n">, which we can enumerate.</p>
<p>However, how many times does a given shape occur? It’s actually not unlike the number of distinct permutations. Given a partition <img src="https://latex.codecogs.com/png.latex?(5,%202,%201)"> giving the shape <img src="https://latex.codecogs.com/png.latex?%5C%7Ba,a,a,a,a,b,b,c%5C%7D">, there are going to be <img src="https://latex.codecogs.com/png.latex?%5Ctext%7BP%7D(10,3)"> possible permutations of values for <img src="https://latex.codecogs.com/png.latex?a">, <img src="https://latex.codecogs.com/png.latex?b">, and <img src="https://latex.codecogs.com/png.latex?c">. Of those permutations, we only care about distinct ones, for which we divide by the number of equivalent permutations <img src="https://latex.codecogs.com/png.latex?5!%0A%5Ctimes%202!%20%5Ctimes%201!"> just like we did earlier.</p>
<p>Combining this with the iteration over the integer partitions, we obtain:</p>
<div id="71cd6a59" class="cell" data-execution_count="4">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> itertools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> product, combinations_with_replacement</span>
<span id="cb8-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="cb8-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> math <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> factorial, perm</span>
<span id="cb8-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> time</span>
<span id="cb8-5"></span>
<span id="cb8-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> distinct_perms(seq):</span>
<span id="cb8-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct permutations of seq."""</span></span>
<span id="cb8-8">    counts <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(seq)</span>
<span id="cb8-9">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> counts.values():</span>
<span id="cb8-11">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(k)</span>
<span id="cb8-12">    val <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> factorial(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(seq)) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb8-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> val</span>
<span id="cb8-14"></span>
<span id="cb8-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> partitions(n, max_part<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>):</span>
<span id="cb8-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Generate all integer partitions of n."""</span></span>
<span id="cb8-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> max_part <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>:</span>
<span id="cb8-18">        max_part <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> n</span>
<span id="cb8-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb8-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> []</span>
<span id="cb8-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span></span>
<span id="cb8-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> first <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">min</span>(n, max_part), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb8-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> rest <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> partitions(n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> first, first):</span>
<span id="cb8-24">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">yield</span> [first] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rest</span>
<span id="cb8-25"></span>
<span id="cb8-26"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> partition_freq(p):</span>
<span id="cb8-27">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Number of distinct prefixes of length n with the shape described by partition p."""</span></span>
<span id="cb8-28">    num <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> perm(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(p))</span>
<span id="cb8-29">    group_sizes <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(p).values()</span>
<span id="cb8-30">    denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-31">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> g <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> group_sizes:</span>
<span id="cb8-32">        denom <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*=</span> factorial(g)</span>
<span id="cb8-33">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> num <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">//</span> denom</span>
<span id="cb8-34"></span>
<span id="cb8-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> expand(p):</span>
<span id="cb8-36">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Turns a partition into a sequence, eg. [4,1,1] -&gt; [1,1,1,1,2,3]"""</span></span>
<span id="cb8-37">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb8-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, count <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(p):</span>
<span id="cb8-39">        result.extend([i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> count)</span>
<span id="cb8-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> result</span>
<span id="cb8-41"></span>
<span id="cb8-42"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">36</span>):</span>
<span id="cb8-43">    start <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time()</span>
<span id="cb8-44">    total <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sum</span>(partition_freq(p) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (distinct_perms(expand(p))<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> p <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> partitions(n))</span>
<span id="cb8-45">    elapsed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> time.time() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> start</span>
<span id="cb8-46">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f'n=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>total<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> (</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>elapsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.1f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">s)'</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stdout">
<pre><code>n=1 10 (0.0s)
n=2 190 (0.0s)
n=3 5140 (0.0s)
n=4 175870 (0.0s)
n=5 7132060 (0.0s)
n=6 329009500 (0.0s)
n=7 16786131400 (0.0s)
n=8 928136093950 (0.0s)
n=9 54775566167500 (0.0s)
n=10 3410496772665940 (0.0s)
n=11 222005754890212600 (0.0s)
n=12 15000987483726651100 (0.0s)
n=13 1046188137708903907000 (0.0s)
n=14 74962723424363171666200 (0.0s)
n=15 5498130019391836779330640 (0.0s)
n=16 411530535654245301470621950 (0.0s)
n=17 31356088574298606320386653100 (0.0s)
n=18 2427021041017043491153965921700 (0.0s)
n=19 190503335542483372330410996741400 (0.0s)
n=20 15141611551571136170888372040881620 (0.0s)
n=21 1217139074924843939858814351399207400 (0.0s)
n=22 98842200132473370747145638230967749800 (0.0s)
n=23 8101728898006244345372661851453503553200 (0.0s)
n=24 669726723998568836522230181105574128110300 (0.0s)
n=25 55795274602503578824222289822443655978969560 (0.0s)
n=26 4681758615768072569523909519240428469710722600 (0.0s)
n=27 395453445930931409251856590032442214866187196400 (0.0s)
n=28 33608239045373840314515158913703013026652947557400 (0.0s)
n=29 2872579519173857793770273182010705032632562354001200 (0.1s)
n=30 246834326193001913394128194670639988506276343043750000 (0.1s)
n=31 21315441958224220856628571224534049914293855923654420000 (0.1s)
n=32 1849273517783123200549693566803651920890240945045791338750 (0.1s)
n=33 161139312619385360063516676533858238406393393904589277787500 (0.1s)
n=34 14098800869487764495194617126968353038429633317749280671202500 (0.1s)
n=35 1238339832632091663366565254379967711395345531463977649183181400 (0.2s)</code></pre>
</div>
</div>

<div class="no-row-height column-margin column-container"><div class="">
<p>Interestingly enough, I couldn’t find such a sequence in the OEIS, so I’ve added it as <a href="https://oeis.org/A394980">A394980</a>.</p>
</div></div><p>Phew! Coming back to the original question, given a random 6 digit TOTP code, there’s a <img src="https://latex.codecogs.com/png.latex?5140/1000000"> probability of getting an interesting TOTP with the second half being an anagram of the first half, or about a 0.5% chance. And if you ever got a 70 digit TOTP, that would occur with a probability of 0.00001238%, which is about the same probability of successfully entering a TOTP that long before it rotates.</p>



 ]]></description>
  <category>Math</category>
  <category>Anagrams</category>
  <category>Combinatorics</category>
  <category>TOTP</category>
  <category>OEIS</category>
  <guid>https://blog.jean-francois.im/posts/palindromes-and-anagrams-in-totp-codes/</guid>
  <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://blog.jean-francois.im/posts/palindromes-and-anagrams-in-totp-codes/images/totp.png" medium="image" type="image/png" height="112" width="144"/>
</item>
<item>
  <title>The Unreasonable Effectiveness of the Superpowers Claude Plugin</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/the-unreasonable-effectiveness-of-the-superpowers-claude-plugin/</link>
  <description><![CDATA[ 






<p>Since we’re now in the era of LLM-based code writing, I’ve been working on writing more code using Claude Code. Obviously, one can say “Hey Claude, I want to build a snake game. I think it would be funny if the snake ate hot dog emojis,” and it’ll probably write something half-decent, but when it comes to larger projects, that kind of one-shot prompt doesn’t work so well.</p>

<div class="no-row-height column-margin column-container"><div class="">
<p>To be fair, LLMs have been relatively decent at doing these one-shot prompts for simple projects in their training data for quite some time.</p>
</div></div><p>Obviously, the next step would be to improve one’s prompting skills. Write a design document, and have the LLM implement it. Maybe even have another LLM review the code.</p>
<p>The interesting thing is that Anthropic added the ability for Claude to have skills, which are basically Markdown files that contain instructions and prompts so that one can leverage the prompting ability of other people to do certain tasks. They’ve also added the ability to add plugins to Claude Code, which can contain multiple skills.</p>
<p>Given those two features, some <a href="https://github.com/obra/superpowers/graphs/contributors">pretty cool people</a> made the <a href="https://claude.com/plugins/superpowers">Superpowers Claude Code plugin</a> which adds skills like <a href="https://github.com/obra/superpowers/blob/3f80f1c769d8a172ee9803d049253f15fbe4895b/skills/brainstorming/SKILL.md">brainstorming</a> that are automatically invoked whenever one uses a prompt similar to the one in the introduction, at which point Claude will start asking questions to clarify what one wants to build.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/superpowers-screenshot.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Claude Code screenshot"><img src="https://blog.jean-francois.im/posts/the-unreasonable-effectiveness-of-the-superpowers-claude-plugin/images/superpowers-screenshot.png" class="img-fluid figure-img" alt="Claude Code screenshot"></a></p>
<figcaption>Claude Code screenshot</figcaption>
</figure>
</div>
<p>It’ll then proceed to draft a design which can then be iterated on through one or more review cycles, then draft an implementation plan, ask for a review, then code the whole thing automatically. Crazy, and after the implementation plan is approved, one can walk away from the computer and come back to something that works — at least if one has their permissions set correctly or runs with <code>--dangerously-skip-permissions</code>.</p>
<p>Obviously, the resulting code is only as good as the quality of the design, and you sometimes have to push back on design decisions that don’t work, but for turning random prompts into quick prototypes or funny snake games embedded in blog posts, it does work unreasonably well.</p>
<canvas id="snake-game" style="display: block; width: 100%; border-radius: 4px;">
</canvas>
<script src="snake.js"></script>



 ]]></description>
  <category>Claude Code</category>
  <category>LLMs</category>
  <guid>https://blog.jean-francois.im/posts/the-unreasonable-effectiveness-of-the-superpowers-claude-plugin/</guid>
  <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Building a Simple Air Quality Monitor</title>
  <dc:creator>Jean-François Im</dc:creator>
  <link>https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/index.en.html</link>
  <description><![CDATA[ 






<p>During the terrible wildfires that we had in California in 2020, it was possible to know how good or bad the outside air quality was through <a href="https://www.purpleair.com/map">PurpleAir’s excellent sensor network</a>.</p>
<p>However, having all the windows closed due to the poor air quality outside, I was wondering about the air quality inside as well. While it’s definitely possible to get pre-made devices — such as <a href="https://www2.purpleair.com/products/purpleair-pa-i-indoor">PurpleAir’s indoor air quality sensor</a>, <a href="https://www.iqair.com/us/air-quality-monitors/airvisual-series">IQAir’s indoor air quality sensor</a>, or even <a href="https://www.aliexpress.com/wholesale?SearchText=air+quality+monitor">something cheapo from AliExpress</a> — building my own is much more interesting:</p>
<ul>
<li>It’s a fun project while being stuck at home during the pandemic</li>
<li>It’s cheaper than a commercial unit, so I can put one upstairs and one downstairs</li>
<li>It’s a good excuse to learn more about electronics</li>
<li>With control over the firmware, it’s possible to do all kinds of things, such as logging the data over serial port or even sending the data over Wi-Fi to a <code>gen_tcp</code> <a href="https://github.com/jfim/air-quality-server">server written in Elixir</a>; once the data is on a more powerful computer, it can be logged to CSV to analyze in R, or one can make a fancy display with Phoenix’s LiveView</li>
<li>Did I mention it’s fun?</li>
</ul>
<p>It’s also a pretty simple project; since all of the parts in the build have passive components included, the project is literally just buying parts, soldering them, putting the parts on a breadboard, and flashing the firmware. As such, a careful beginner could assemble this relatively easily.</p>
<p>Since it’s always a good idea to have an idea of what one is getting into before starting a project, the unit costs should be relatively low — less than USD$10 for just temperature and humidity to less than USD$70 for CO<sub>2</sub>, PM1.0/2.5/10.0 and tVOC depending on the cost of sensors — although getting the basic electronics tools required for this might add a bit to this. Time wise, this project can be completed in a few hours, with about an hour or two for each of the phases of this project — shopping, soldering, assembling, and installing the firmware — although one can tinker with the software for a while.</p>
<p>If you follow the instructions in this post, you’ll have this assembly on a breadboard, with data that can be read over the serial port or sent over Wi-Fi to a server of your own for further processing. It is powered through a USB port on your computer or any decent USB charger. This is what it looks like:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/breadboard.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Everything assembled on a breadboard"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/breadboard.jpg" class="img-fluid figure-img" alt="Everything assembled on a breadboard"></a></p>
<figcaption>Everything assembled on a breadboard</figcaption>
</figure>
</div>
<p>Since this is customizable, one could also opt for a very minimal version with a single sensor, such as this one.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/mini-breadboard.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="A cute miniature sensor"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/mini-breadboard.jpg" class="img-fluid figure-img" alt="A cute miniature sensor"></a></p>
<figcaption>A cute miniature sensor</figcaption>
</figure>
</div>
<p>And this is an example of a display that one can build (will be explained in a follow up post):</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/display.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Laptop and eInk display"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/display.jpg" class="img-fluid figure-img" alt="Laptop and eInk display"></a></p>
<figcaption>Laptop and eInk display</figcaption>
</figure>
</div>
<p>Since this is a learning project for me, I also started designing my first PCB for this, as well as an enclosure that can be 3D printed; in theory, the whole thing should fit within a pretty compact package.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/assembly.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Solidworks PCB assembly"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/assembly.png" class="img-fluid figure-img" alt="Solidworks PCB assembly"></a></p>
<figcaption>Solidworks PCB assembly</figcaption>
</figure>
</div>
<p>Neither the PCB nor the enclosure are finished, but once they’re ready and tested, I’ll post the files here.</p>
<section id="the-hardware" class="level2">
<h2 class="anchored" data-anchor-id="the-hardware">The hardware</h2>
<p><a href="https://www.airgradient.com/diy/">Air Gradient’s DIY sensor</a> has a relatively similar BOM with an interesting combination of sensors. However, I wasn’t too much of a fan of their design; their build uses an older version of the Plantower particle sensor which is much larger, the CO<sub>2</sub> sensor is soldered upside-down such that the CO<sub>2</sub> permeable membrane faces the PCB instead of ambient air, all of the sensors are soldered directly to the PCB so that they can’t be reused/replaced, and their PCB seems a bit too large to place on a windowsill or other narrow location. I also didn’t care too much about the OLED display.</p>
<p>As such, this is the BOM that I have for this project, which is slightly different:</p>
<ul>
<li>WeMos D1 mini (ESP8266 board)</li>
<li>SenseAir S8 CO<sub>2</sub> sensor</li>
<li>Plantower PMSA003-A particle sensor</li>
<li>SHT31 temperature and humidity sensor board</li>
<li>SGP30 tVOC sensor</li>
</ul>
<p>All of the hardware isn’t necessary, so if you don’t care about CO<sub>2</sub> or particle counting, you can take that out of the build, it’ll still work.</p>
<section id="potential-modifications" class="level3">
<h3 class="anchored" data-anchor-id="potential-modifications">Potential modifications</h3>
<p>If you’re planning on just following the instructions in the rest of this post, you can skip this section.</p>
<p>It <em>might</em> be possible to use <a href="https://store.particle.io/collections/dev-kits/products/argon-kit">Particle’s Argon board</a> instead of the WeMos D1 mini; I haven’t tried it, but it would have the advantage of having the charging hardware for LiPo batteries and integration with their IoT platform. They also have <a href="https://docs.particle.io/air-quality-monitoring-kit/">an air quality monitoring kit that has a different set of sensors</a>, for the soldering averse.</p>
<p>Another thing that I might change is replacing the SenseAir S8 CO<sub>2</sub> sensor with a different CO<sub>2</sub> sensor such as Sensirion’s SCD30; while the S8 works, the datasheet specifies that the average current usage is 30 mA with a peak of 300 mA, which seems to exceed the max 500mA at 5V that a USB port offers by default, leading to a bit of a voltage drop every few seconds. If the voltage drops too low, it seems that the SenseAir S8 returns erroneously high readings; if that’s the case, using a different USB power source and shorter USB cables may help.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="images/5v-rail-scope.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="5V rail voltage drop due to the SenseAir S8"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/5v-rail-scope.jpg" class="img-fluid figure-img" alt="5V rail voltage drop due to the SenseAir S8"></a></p>
<figcaption>5V rail voltage drop due to the SenseAir S8</figcaption>
</figure>
</div>
<p>Alternatively, since the SenseAir S8 CO<sub>2</sub> is the most expensive part of this whole build, it could be replaced by the eCO<sub>2</sub> approximation done by the SGP30. It won’t be as accurate, but depending on your use it might be “good enough.”</p>
</section>
<section id="shopping-list" class="level3">
<h3 class="anchored" data-anchor-id="shopping-list">Shopping list</h3>
<p>If like me you didn’t have anything that could be used to make electronics at home, here is a shopping list. These items can be bought from a local electronics distributor (eg. Mouser, DigiKey, AdaFruit, SparkFun, etc.) or AliExpress.</p>
<ul>
<li>The parts that you want for your sensor:
<ul>
<li>WeMos D1 mini <img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/wemos-d1-mini.jpg" class="img-fluid"></li>
<li>SenseAir S8 CO<sub>2</sub> sensor (if you want a CO<sub>2</sub> sensor) <img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/senseair-s8.jpg" class="img-fluid"></li>
<li>Plantower PMSA003-A particle sensor (if you want a sensor for PM1.0/2.5/10.0, good keywords for this on AliExpress are “PMSA003” or “Plantower dust sensor”) Other Plantower sensor models such as the PMS7003, PMS6003, PMS5003, PMS3003, and PMS1003 /should/ also work but will be larger. The letter after the model name refer to the orientation of the airflow in the sensor; if you’re not building a PCB and case, it doesn’t really matter which one you get. You’ll want to make sure that your sensor comes with a little rainbow colored cable, so that you can solder a connector to it as in this picture. <img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/plantower-pmsa003.jpg" class="img-fluid"></li>
<li>If you want to avoid having to strip and solder wires, which is a bit finnicky, there is a breakout board that will give you male pin headers that’s available (search for “G7 adapter” on <a href="https://www.aliexpress.com/store/1725971">double lung electronic</a>’s AliExpress shop).</li>
<li>SHT31 module (also referred as SHT3X, if you want a sensor for temperature and humidity, good keywords for this on AliExpress are “SHT30,” “SHT31,” should be a small purple board) <img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/sht31.jpg" class="img-fluid"></li>
<li>SGP30 module (if you want tVOC sensing, and an approximate CO<sub>2</sub> sensing, good keywords for this on AliExpress are “SGP30” and “GY-SGP,” should be a small blue board) <img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/sgp30.jpg" class="img-fluid"></li>
</ul></li>
<li>A micro USB cable</li>
<li>A soldering iron: I got a Hakko FX888D but any soldering iron should do</li>
<li>Some solder: I used Kester 24-6337-0018 leaded Sn63Pb37 rosin activated solder, but any leaded rosin solder should do. You probably want to get solder wire that is relatively thin to make it easier to solder these parts.</li>
<li>A fume extractor; breathing solder fumes isn’t good for your health, I got the Aven 17701</li>
<li>Some flux if you’re planning on soldering the wires from the particle sensor: I got a small bottle of Kester 959T flux from <a href="https://www.cmlsupply.com/">CMLsupply.com</a></li>
<li>A few 2.54mm male pin headers</li>
<li>Needle nose pliers to break off the male pin headers cleanly</li>
<li>Small wire cutters to cut and strip the particle sensor wires</li>
<li>A breadboard and jumper wires; I got a cheap one off AliExpress and regretted it, it had a short in it. Make sure it is wide enough if you’re using the SenseAir S8 CO<sub>2</sub> sensor, as that sensor is too wide to fit on a single breadboard.</li>
<li>A basic multimeter for continuity testing; if you have an oscilloscope or a logic analyzer handy, they might help with debugging if you encounter issues, but I wouldn’t buy them solely for this project</li>
</ul>
<p>If you’re getting parts off AliExpress, arm yourself with some patience as it’ll take a while for everything to arrive.</p>
</section>
</section>
<section id="assembling-everything" class="level2">
<h2 class="anchored" data-anchor-id="assembling-everything">Assembling everything</h2>
<p>With all of the parts needed, what’s required is to solder the appropriate pin headers to the various module boards. For electronics beginners, you’ll want to search for <a href="https://duckduckgo.com/?q=how+to+solder+electronics">how to solder electronics</a> before starting to solder this.</p>
<p>If you’re not too sure about your soldering skills, solder the SHT31 module and the WeMos D1 mini first, as they’re the easiest parts to solder. You can then assemble the sensor to check that everything works, then come back to do the more expensive components.</p>
<section id="soldering" class="level3">
<h3 class="anchored" data-anchor-id="soldering">Soldering</h3>
<p>For the SHT31 module, you want to solder the pins such that the SHT31 sensor (the tiny chip with the little hole in the middle) faces up when placed on a breadboard.</p>
<p><a href="images/sht31.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-6"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/sht31.jpg" class="img-fluid"></a></p>
<p>For the WeMos D1 mini, you want to solder the pins pointing below, as shown in this picture.</p>
<p><a href="images/wemos-d1-mini.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-7"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/wemos-d1-mini.jpg" class="img-fluid"></a></p>
<p>For the Plantower particle sensor, you’ll want to cut the rainbow cable in half, strip the wires, and solder some pins to the wires. You want to make sure to solder the VCC, GND, RX and TX wires. You can solder the RST and SET wires, but they’re not used in this build.</p>
<p><a href="images/plantower-pmsa003.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-8"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/plantower-pmsa003.jpg" class="img-fluid"></a></p>
<p>For the SenseAir S8 CO<sub>2</sub> sensor, you’ll want to put the pins so that the pins stick out towards the body of the sensor. To do so, take some of the pin sockets from the WeMos D1 mini and place them on your breadboard. Break off two sections of pin headers (4 and 5 pins respectively), place them in the pin sockets, then place the S8 and solder away. The result should look like this.</p>
<p><a href="images/senseair-s8.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-9"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/senseair-s8.jpg" class="img-fluid"></a></p>
</section>
<section id="assembling" class="level3">
<h3 class="anchored" data-anchor-id="assembling">Assembling</h3>
<p>Now that you have the parts that you need soldered, you need to put them on a breadboard according to this schematic.</p>
<p><a href="images/sensor-schematic.png" class="lightbox" data-gallery="quarto-lightbox-gallery-10"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/sensor-schematic.png" class="img-fluid"></a></p>
<p>For the rest of us, this means that all of the 5V pins should be connected to the 5V pin on the WeMos D1 mini, all of the 3.3V pins should be connected to the 3.3V pin on the WeMos D1 mini, all of the grounds should be connected together, SCL and SDA should be connected to the SCL and SDA pins on the WeMos D1 mini, and the RX and TX pins from the other sensors should be connected to the pins indicated on the diagram.</p>
<p>For electronics beginners, you’ll want to search for <a href="https://duckduckgo.com/?q=how+to+use+a+breadboard">how to use a breadboard</a> before starting to assemble this on a breadboard.</p>
<p>In my case, I’ve used the left side of the breadboard for 3.3V and the right side of the breadboard for 5V; both grounds should be connected together.</p>
<p>If you’re using the CO<sub>2</sub> sensor, you’ll want to put some pin sockets on the breadboard so that you can put the sensor in the pin sockets. The pins on the S8 are not very well labeled, so you’ll want to refer to the <a href="https://duckduckgo.com/?q=senseair+s8+datasheet">SenseAir S8 datasheet</a>, page 3. With the PCB pointing away from the breadboard, the pins on the left from the top are G+, G0, OC, and PWM; on the right, starting from the top, they’re DVCC, RX, TX, RT, and CAL.</p>
<p>If you do it right, it should look like this:</p>
<p><a href="images/breadboard.jpg" class="lightbox" data-gallery="quarto-lightbox-gallery-11"><img src="https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/images/breadboard.jpg" class="img-fluid"></a></p>
<p>To make sure that everything works right before plugging in the USB cable, use your multimeter on the continuity testing setting. For electronics beginners, you’ll want to search for <a href="https://duckduckgo.com/?q=multimeter+continuity+testing">multimeter continuity testing</a> to learn how to do this. Make sure that all of the pins are connected as expected:</p>
<ul>
<li>All of the pins labeled GND/G0 should be connected to the G pin on the WeMos D1 mini</li>
<li>The 5V pin should be connected to the G+ pin on the S8 and the VCC pin of the Plantower</li>
<li>The 3V3 pin should be connected to the VIN pin of the SHT31 and SGP30</li>
<li>The SCL and SDA pins should be connected to the D1 and D2 pins of the WeMos D1 mini, respectively (eg. SCL connected to D1, SDA connected to D2)</li>
<li>The RX and TX pins of the particle sensor should be connected to the D5 and D6 pins of the WeMos D1 mini, respectively</li>
<li>The RX and TX pins of the CO<sub>2</sub> sensor should be connected to the D0 and D7 pins of the WeMos D1 mini, respectively</li>
<li>There should not be any other connections than the ones listed above</li>
</ul>
</section>
</section>
<section id="the-software" class="level2">
<h2 class="anchored" data-anchor-id="the-software">The software</h2>
<p>I’ve created a <a href="https://github.com/jfim/air-quality-monitor-firmware">PlatformIO project and put it on GitHub</a>. If you’re using the D1 mini, you can probably use it as is; I use the PlatformIO extension from VS Code. Make sure that you also have the <a href="https://www.wemos.cc/en/latest/ch340_driver.html">CH340 driver installed from the WeMos site</a>, as you’ll need it to flash the firmware using USB.</p>
<p>You can configure the project by editing the <code>platformio.ini</code> file, which tells PlatformIO which sensors to use and how to build and flash the firmware.</p>
<p>The first time that you flash the firmware, you’ll want to do it over USB, as the D1 mini isn’t running the software that would allow flashing it over the air yet. You’ll also want to have the <code>ENABLE_SERIAL_DEBUGGING</code> option enabled to make sure that you can get readings from the sensors.</p>
<p>Flash the firmware using PlatformIO and open the serial monitor; you should be seeing output similar to the one below. If everything looks good, then you can remove the serial debugging option.</p>
<p>Based on what you want to do, you can leave the air quality monitor attached to a computer and have it log data over the serial port; it’ll emit a single line every second or so that contains all of the sensor readings.</p>
<p>Alternatively, you can enable the network settings to have it connect to 2.4GHz Wi-Fi and send its data over a TCP socket. It’ll emit the same data as over the serial port, but it’ll do so over the network so that you can have multiple sensors that log data to a server. This also enables over the air flashing, so that you can update the firmware on the D1 mini without having to unplug it, plug it into a computer, then put it back into place.</p>
<p>If you want to go that route, the first time that you’ll boot the sensor, it’ll create an unsecured AP. Connecting to that AP shows a page that allows you to configure the Wi-Fi network to connect to, which will be remembered.</p>
<p>I have made <a href="https://github.com/jfim/air-quality-server">a simple server in Elixir</a> that will save the data every few minutes to a CSV file, and rotate the files such that there is one file per day. This should allow you to start playing with the sensor data in your favorite data science environment.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>This was a pretty fun project! I still need to finish the PCB and case, but I’ve been running these air quality monitors for a few months now and it’s been pretty interesting to look at the data.</p>
<p>I’ll write a subsequent post that talks about the things I have learned about indoors air quality by monitoring the air quality at my place.</p>
<p>If you build your own air quality monitor, feel free to reach out; I’ll be happy to hear about how you built your own and what you’ve learned from it!</p>


</section>

 ]]></description>
  <category>Projects</category>
  <category>Electronics</category>
  <category>Air Quality</category>
  <guid>https://blog.jean-francois.im/posts/building-a-simple-air-quality-monitor/index.en.html</guid>
  <pubDate>Sat, 08 May 2021 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
