<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>lo.calho.st</title>
    <link>https://lo.calho.st/</link>
    <description>Recent content on lo.calho.st</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 13 Sep 2025 00:00:00 +0000</lastBuildDate>
    
	<atom:link href="https://lo.calho.st/index.xml" rel="self" type="application/rss+xml" />
    
    
    
    <item>
      <title>The nature of AI discourse has changed</title>
      <link>https://lo.calho.st/posts/complaining-about-ai/</link>
      <pubDate>Sat, 13 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/complaining-about-ai/</guid>
      <description>&lt;p&gt;I recently made a post in which I complained about AI and said I wouldn&amp;rsquo;t be
posting about it again. That was a lie. I&amp;rsquo;m back. But this time instead of
dumping my own thoughts on the subject I&amp;rsquo;m just going to be linking other blogs and
articles. These aren&amp;rsquo;t all strictly about AI &amp;ndash; some related topics are included here.
Hopefully you can infer why.&lt;/p&gt;
&lt;p&gt;Why am I doing this? A couple months ago, I felt myself under attack by a constant
barrage of AI hype and blind optimism. Over the past month or two, the negative press
seems to have overtaken it. This feels like a historic moment to me and I want to
document it.&lt;/p&gt;
&lt;p&gt;This is a merely collection of articles I have read or intend to read in the near future &amp;ndash;
that&amp;rsquo;s right, I have not read all of them yet. However, I have skimmed them enough to create a
coarse taxonomy so I could group related topics here. I am excluding articles that may seem
relevant to the thesis if I already linked to them in my previous blog.&lt;/p&gt;
&lt;p&gt;For the purposes of this post, note that &amp;ldquo;AI&amp;rdquo; refers to generative AI, particularly LLMs.&lt;/p&gt;
&lt;h2 id=&#34;ai-is-breaking-shit-making-us-dumb-or-otherwise-contributing-to-shit-being-broken&#34;&gt;AI Is Breaking Shit, Making Us Dumb, or Otherwise Contributing to Shit Being Broken&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.val.town/vibe-code&#34; target=&#34;_blank&#34; &gt;Vibe code is legacy code&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://deepdocs.dev/ai-and-technical-debt-a-race-to-the-bottom/&#34; target=&#34;_blank&#34; &gt;AI vs Technical Debt: Is This A Race to the Bottom?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.bloomberg.com/news/articles/2025-08-12/ai-eroded-doctors-ability-to-spot-cancer-within-months-in-study&#34; target=&#34;_blank&#34; &gt;AI Eroded Doctors’ Ability to Spot Cancer Within Months in Study&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.finalroundai.com/blog/what-ctos-think-about-vibe-coding&#34; target=&#34;_blank&#34; &gt;What CTOs Really Think About Vibe Coding&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;ai-is-evil-hellbent-on-destroying-our-careers-and-lives&#34;&gt;AI Is Evil, Hellbent on Destroying Our Careers and Lives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://victorwynne.com/developers-not-operators/&#34; target=&#34;_blank&#34; &gt;Developers, not operators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://pluralistic.net/2025/04/27/some-animals/#are-more-equal-than-others&#34; target=&#34;_blank&#34; &gt;The enshittification of tech jobs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://buttondown.com/monteiro/archive/how-to-not-build-the-torment-nexus/&#34; target=&#34;_blank&#34; &gt;How to not build the Torment Nexus&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.quantamagazine.org/the-ai-was-fed-sloppy-code-it-turned-into-something-evil-20250813/&#34; target=&#34;_blank&#34; &gt;The AI Was Fed Sloppy Code. It Turned Into Something Evil.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;ai-is-stupid-expensive-and-useless&#34;&gt;AI Is Stupid, Expensive, and Useless&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://leaddev.com/velocity/ai-coding-assistants-arent-really-making-devs-feel-more-productive&#34; target=&#34;_blank&#34; &gt;AI coding assistants aren’t really making devs feel more productive&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://colton.dev/blog/curing-your-ai-10x-engineer-imposter-syndrome/&#34; target=&#34;_blank&#34; &gt;No, AI is not Making Engineers 10x as Productive&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.pcgamer.com/software/platforms/googles-gemini-ai-tells-a-redditor-its-cautiously-optimistic-about-fixing-a-coding-bug-fails-repeatedly-calls-itself-an-embarrassment-to-all-possible-and-impossible-universes-before-repeating-i-am-a-disgrace-86-times-in-succession/&#34; target=&#34;_blank&#34; &gt;Google&amp;rsquo;s Gemini AI tells a Redditor it&amp;rsquo;s &amp;lsquo;cautiously optimistic&amp;rsquo; about fixing a coding bug, fails repeatedly, calls itself an embarrassment to &amp;lsquo;all possible and impossible universes&amp;rsquo; before repeating &amp;lsquo;I am a disgrace&amp;rsquo; 86 times in succession&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.lightreading.com/ai-machine-learning/ai-looks-increasingly-useless-in-telecom-and-anywhere-else&#34; target=&#34;_blank&#34; &gt;AI looks increasingly useless in telecom and anywhere else&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.wheresyoured.at/the-haters-gui/&#34; target=&#34;_blank&#34; &gt;The Hater&amp;rsquo;s Guide To The AI Bubble&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://www.interviewquery.com/p/mit-ai-isnt-replacing-workers-just-wasting-money&#34; target=&#34;_blank&#34; &gt;MIT says AI isn’t replacing you… it’s just wasting your boss’s money&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;ai-outright-sucks&#34;&gt;AI Outright Sucks&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;n.b.: These authors have provided comprehensive lists of reasons why AI sucks. In a way, it therefore it spans all of the categories above, and then some.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://malwaretech.com/2025/08/every-reason-why-i-hate-ai.html&#34; target=&#34;_blank&#34; &gt;Every Reason Why I Hate AI and You Should Too&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://anthonymoser.github.io/writing/ai/haterdom/2025/08/26/i-am-an-ai-hater.html&#34; target=&#34;_blank&#34; &gt;I Am An AI Hater&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Homelab updates</title>
      <link>https://lo.calho.st/posts/homelab-updates/</link>
      <pubDate>Sat, 19 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/homelab-updates/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been a couple years since my &lt;a href=&#34;https://lo.calho.st/posts/homelab/&#34; &gt;last homelab post&lt;/a&gt;
and many things have changed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;diagram.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;There are more VLANs now. Retained from last time, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One for IoT crap.&lt;/li&gt;
&lt;li&gt;One for surveilance.&lt;/li&gt;
&lt;li&gt;One for Home Assistant.&lt;/li&gt;
&lt;li&gt;One for work.&lt;/li&gt;
&lt;li&gt;One for the Steam Link and gaming VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The new additions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One for a media server, and&lt;/li&gt;
&lt;li&gt;One for WAN.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The SSIDs haven&amp;rsquo;t changed from last time, and it&amp;rsquo;s all still
Unifi gear (excluding the modem). I upgraded from a USG
to a UDM-Pro (and got rid of the UNVR in the process), and
consolidated the other infra extensively.&lt;/p&gt;
&lt;p&gt;My new place has Ethernet in the walls, so I stuck the old
8p POE switch in the media box where all the drops terminate.
All the other major stuff gets to live on the wall or floor next
to it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;wallmount.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;One exception is the modem, which is in the garage because
that&amp;rsquo;s where the fiber was dropped. Fortunately there was also Ethernet
back to the media box from there, so I VLAN&amp;rsquo;d the WAN and am POEing
the modem.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&#34;https://lo.calho.st/posts/new-server-part-2/&#34; &gt;VM server&lt;/a&gt;
is the only persistent piece of iron on the network now,
so I was able to sell off the rackmount switches.&lt;/p&gt;
&lt;p&gt;This is my first time trying out rackstuds and they&amp;rsquo;re actually pretty
nice.&lt;/p&gt;
&lt;p&gt;Unfortunately I wasn&amp;rsquo;t smart enough with the positioning to be able to also
stick the UPS on the wall.&lt;/p&gt;
&lt;p&gt;I still need 3 APs to get decent coverage. The UAP-AC-Pro is in that
same closet, while the IW-HD is by the TV and the FlexHD is upstairs
in the bedroom.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>I tried AI and all I got was disappointed</title>
      <link>https://lo.calho.st/posts/i-tried-an-ai/</link>
      <pubDate>Sat, 19 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/i-tried-an-ai/</guid>
      <description>&lt;p&gt;This is another post that I began a long time ago and never finished: it was
originally dated 2024-05-05 but I think I started working on it even earlier.&lt;/p&gt;
&lt;p&gt;At the time, the GPT hype cycle was already well underway, so I finally gave in
and decided to give AI a serious try.&lt;/p&gt;
&lt;h2 id=&#34;prologue-setting-up-a-local-chatbot&#34;&gt;Prologue: Setting up a local chatbot&lt;/h2&gt;
&lt;p&gt;I didn&amp;rsquo;t want to give OpenAI any of my money, so I set up a VM with a GPU
passed through.&lt;/p&gt;
&lt;p&gt;I originally wrote &amp;ldquo;I want to be clear that the GPU was already in the box from when I did
my &lt;a href=&#34;https://lo.calho.st/posts/gaming-vm/&#34; &gt;gaming VM&lt;/a&gt;; I did not have the ambition to
go that far out of my way to do this.&amp;rdquo; &amp;ndash; however when I rebuilt my server I decided to add
a Tesla dedicated to this project.&lt;/p&gt;
&lt;p&gt;Ultimately, I settled on llama-cpp-python for the backend. I tried LocalAI, too, but ultimately
ditched it because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The prebuilt image assumes you&amp;rsquo;re not running on decade-old hardware, so I got SIGILL&amp;rsquo;d&lt;/li&gt;
&lt;li&gt;So I had to build from source, which took forever&lt;/li&gt;
&lt;li&gt;It very badly wanted to be run in Docker&lt;/li&gt;
&lt;li&gt;Docker images with CUDA in them are like 50 GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was therefore way easier to just build and install llama-cpp-python and run it as a systemd
service, since I didn&amp;rsquo;t really need any of the additional features.&lt;/p&gt;
&lt;p&gt;For the frontend, I ended up with big-AGI. I liked librechat too, but it had some UX issues
(kept logging me out, didn&amp;rsquo;t like to persist model preferences between chats, etc).&lt;/p&gt;
&lt;p&gt;I ended up sticking with Mistral-7B-instruct for most use - I tried some other Mistral variants
but this one seemed to be the least likely to start hallucinating when given short prompts.&lt;/p&gt;
&lt;h2 id=&#34;chapter-1-ai-helps-me-create-a-nodejs-project&#34;&gt;Chapter 1: AI helps me create a node.js project&lt;/h2&gt;
&lt;p&gt;Now that I had a robot to ask questions to, I embarked on a project: getting AI to help me build a web app for a
random domain I purchased on a whim because it sounded cool. I am not a web developer at all, and haven&amp;rsquo;t done any
web stuff since when jQuery was considered a Javascript framework, the best CSS we had was 2.1, and we all still
put those &amp;ldquo;Valid XHTML 1.1&amp;rdquo; badges in our footers. Therefore, I thought this would be a good exercise: I could pretend
to be a newbie and AI could help me do things I otherwise couldn&amp;rsquo;t. If it went well, then I&amp;rsquo;d know my job really is
at risk.&lt;/p&gt;
&lt;h2 id=&#34;there-is-no-chapter-2&#34;&gt;There is no chapter 2&lt;/h2&gt;
&lt;p&gt;I spent a week really trying to get Mistral to teach me React. It failed at providing up-to-date guidance on initial setup
for a project, so I ended up doing it the old fashioned way (googling). I tried to get it to produce the equivalent
of a hello world page, but it couldn&amp;rsquo;t do that either. I flailed for a while longer then abandoned the project.&lt;/p&gt;
&lt;p&gt;If you are about to say &amp;ldquo;skill issue,&amp;rdquo; just shut up. I am told time and time again that LLMs can do great things. But if I
try to do something complicated and it screws up, the story is &amp;ldquo;that&amp;rsquo;s too hard, it&amp;rsquo;s better at simple tasks.&amp;rdquo; I gave it
the simplest task I could think of &amp;ndash; an ideal use case per all the discourse online &amp;ndash; and it couldn&amp;rsquo;t handle it.&lt;/p&gt;
&lt;p&gt;If you were gonna say &amp;ldquo;that model sucks,&amp;rdquo; also shut up. What reason do I have to expect a paid service to be better, and not
just a waste of my money? If you think it&amp;rsquo;s because a newer model would be better at the job, then why were we already hyping
this shit up? This has been discussed
&lt;a href=&#34;https://blog.glyph.im/2025/06/i-think-im-done-thinking-about-genai-for-now.html&#34; target=&#34;_blank&#34; &gt;time&lt;/a&gt;
&lt;a href=&#34;https://ludic.mataroa.blog/blog/i-will-fucking-piledrive-you-if-you-mention-ai-again/&#34; target=&#34;_blank&#34; &gt;and time&lt;/a&gt;
&lt;a href=&#34;https://www.wheresyoured.at/optimistic-cowardice/&#34; target=&#34;_blank&#34; &gt;again&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;believe-me-i-really-tried&#34;&gt;Believe me, I really tried&lt;/h2&gt;
&lt;p&gt;I admit my stupid experiment had limitations, so I tried using an AI coding assistant at my day job for a few months, too.
We had a model trained on internal code, touted to be able to deal with the nuances of our frameworks, etc., but it also
sucked. If it didn&amp;rsquo;t outright hallucinate, the code it generated usually smelled; it failed at all but basic tasks, and the
IDE integration was buggy and kept crashing.&lt;/p&gt;
&lt;p&gt;It was at least somewhat competent at generating boilerplate, but as I began to rely on it for that I could feel it making me dumb.
This wasn&amp;rsquo;t just hypochondria &amp;ndash;
&lt;a href=&#34;https://www.microsoft.com/en-us/research/wp-content/uploads/2025/01/lee_2025_ai_critical_thinking_survey.pdf&#34; target=&#34;_blank&#34; &gt;there is evidence of this phenomena&lt;/a&gt;. I uninstalled it and never looked back.&lt;/p&gt;
&lt;p&gt;To this day, generative AI forces itself into my daily life in ways I do not consent to. I have to review slop PRs submitted
by my coworkers. They&amp;rsquo;re getting worse and worse. I have to read slop summaries of my own PRs and tickets, forcibly inserted
by the tools themselves. The analysis, if it is even accurate, is consistently surface level at best. It&amp;rsquo;s usually just
verbosely paraphrasing the commit message. It provides no value.&lt;/p&gt;
&lt;p&gt;You can&amp;rsquo;t google a basic question without risk of getting a hallucinated answer. Even duckduckgo has inserted
slop above all the actual search results.&lt;/p&gt;
&lt;h2 id=&#34;i-do-not-believe-in-this-technology&#34;&gt;I do not believe in this technology&lt;/h2&gt;
&lt;p&gt;LLMs are a brute force approach to &lt;em&gt;approximately&lt;/em&gt; accomplishing anything. I won&amp;rsquo;t rattle on about the energy concerns,
because this has already been discussed. But for what, a cheap parlor trick? It fundamentally cannot reason.
It cannot exercise creativity, except in the eerie way it confidently lies about things that should be fact.
I&amp;rsquo;m tired of hearing about it and I no longer want to write about it, either.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Raptor Lake iGPU SR-IOV</title>
      <link>https://lo.calho.st/posts/sr-iov-raptor-lake/</link>
      <pubDate>Sat, 19 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/sr-iov-raptor-lake/</guid>
      <description>&lt;h2 id=&#34;a-madman-embarks-on-a-quest&#34;&gt;A madman embarks on a quest&lt;/h2&gt;
&lt;p&gt;The first line of this post was originally going to be &amp;ldquo;Last week I posted about
&lt;a href=&#34;https://lo.calho.st/posts/new-server-part-2/&#34; &gt;getting my wacky new server working&lt;/a&gt;.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;It is no longer last week, but rather last year. Yes, I started writing this in June 2024
and it is now July 2025.&lt;/p&gt;
&lt;p&gt;This server originally had two discrete GPUs for VMs to use, but I also wanted to leverage the
i9&amp;rsquo;s iGPU to handle video transcoding for my media host.&lt;/p&gt;
&lt;p&gt;Even before this post was delayed by a year, I had decided that this would not be a guide, but rather a rant.&lt;/p&gt;
&lt;h2 id=&#34;will-this-be-easy&#34;&gt;Will this be easy?&lt;/h2&gt;
&lt;p&gt;My original goal was to just load a driver, create a VirGL device, and use it for stuff. It wasn&amp;rsquo;t that easy. During boot, I
got this cool message:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[   21.856166] xe 0000:00:02.0: Your graphics device a780 is not officially supported
               by xe driver in this kernel version. To force Xe probe,
               use xe.force_probe=&amp;#39;a780&amp;#39; and i915.force_probe=&amp;#39;!a780&amp;#39;
               module parameters or CONFIG_DRM_XE_FORCE_PROBE=&amp;#39;a780&amp;#39; and
               CONFIG_DRM_I915_FORCE_PROBE=&amp;#39;!a780&amp;#39; configuration options.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright, sounds easy. Let&amp;rsquo;s just try this out: &lt;code&gt;modprobe xe force_probe=&#39;a780&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nice, a device appears in &lt;code&gt;/dev/dri&lt;/code&gt;. Time to attach a VirGL device to a VM and boot it up.&lt;/p&gt;
&lt;h2 id=&#34;spoiler-alert-it-didnt-work&#34;&gt;Spoiler alert: it didn&amp;rsquo;t work&lt;/h2&gt;
&lt;p&gt;The tl;dr is that this afforded me no VA-API capabilities. I did some googling, but have now lost the links
I found. If I recall correctly, I think the conclusion was that the xe driver can&amp;rsquo;t do what I want when
paravirtualized. I tried using i915 too, but it won&amp;rsquo;t even load &amp;ndash; even with the force_probe stuff.&lt;/p&gt;
&lt;p&gt;This iGPU supports SR-IOV: some magic to make it look like &lt;em&gt;multiple&lt;/em&gt; GPUs so each can be passed through into
its own VM. Perfect, then I don&amp;rsquo;t have to worry about this paravirtualization nonsense.&lt;/p&gt;
&lt;p&gt;The kernel doesn&amp;rsquo;t support this by default, but &lt;a href=&#34;https://github.com/strongtz/i915-sriov-dkms&#34; target=&#34;_blank&#34; &gt;there&amp;rsquo;s a DKMS module&lt;/a&gt;.
Let&amp;rsquo;s go ahead and build it!&lt;/p&gt;
&lt;h2 id=&#34;compromises&#34;&gt;Compromises&lt;/h2&gt;
&lt;p&gt;When I first started trying to get SR-IOV working, I was upset that the aforementioned DKMS package didn&amp;rsquo;t support
the 6.8 kernel. I followed instructions in the repo to pin to an earlier one. I figured I could
live with 6.5. I rebooted the system and turned on SR-IOV in the bios.&lt;/p&gt;
&lt;p&gt;I cloned the repo and followed the steps in the readme, adjusting for my kernel version.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;root@kagrenac:~# cd i915-sriov-dkms
root@kagrenac:~/i915-sriov-dkms# uname -a
Linux kagrenac 6.5.13-5-pve #1 SMP PREEMPT_DYNAMIC PMX 6.5.13-5 (2024-04-05T11:03Z) x86_64 GNU/Linux
root@kagrenac:~/i915-sriov-dkms# vi dkms.conf # change the package name and version
root@kagrenac:~/i915-sriov-dkms# cd ..
root@kagrenac:~# mv i915-sriov-dkms /usr/src/i915-sriov-dkms-6.5.13-5-pve
root@kagrenac:~# apt install proxmox-headers-6.5.13-5-pve
...
root@kagrenac:~# dkms install -m i915-sriov-dkms -v 6.5.13-5-pve
Sign command: /lib/modules/6.5.13-5-pve/build/scripts/sign-file
Signing key: /var/lib/dkms/mok.key
Public certificate (MOK): /var/lib/dkms/mok.pub
Creating symlink /var/lib/dkms/i915-sriov-dkms/6.5.13-5-pve/source -&amp;gt; /usr/src/i915-sriov-dkms-6.5.13-5-pve

Building module:
Cleaning build area...
Building module(s)......(bad exit status: 2)
Failed command:
make -j32 KERNELRELEASE=6.5.13-5-pve -C /lib/modules/6.5.13-5-pve/build M=/var/lib/dkms/i915-sriov-dkms/6.5.13-5-pve/build KVER=6.5.13-5-pve
Error! Bad return status for module build on kernel: 6.5.13-5-pve (x86_64)
Consult /var/lib/dkms/i915-sriov-dkms/6.5.13-5-pve/build/make.log for more information
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;uncharted-territory&#34;&gt;Uncharted territory&lt;/h2&gt;
&lt;p&gt;I checked out the log and found the error:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/var/lib/dkms/i915-sriov-dkms/6.5.13-5-pve/build/drivers/gpu/drm/i915/display/intel_dp_mst.c: In function ‘intel_dp_mst_find_vcpi_slots_for_bpp’:
/var/lib/dkms/i915-sriov-dkms/6.5.13-5-pve/build/drivers/gpu/drm/i915/display/intel_dp_mst.c:86:43: error: too many arguments to function ‘drm_dp_calc_pbn_mode’
   86 |                         crtc_state-&amp;gt;pbn = drm_dp_calc_pbn_mode(adjusted_mode-&amp;gt;crtc_clock,
      |                                           ^~~~~~~~~~~~~~~~~~~~
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I tried to brush off the &amp;ldquo;kernel module dev&amp;rdquo; hat that I hadn&amp;rsquo;t worn since probably 2015.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;root@kagrenac:/usr/src/i915-sriov-dkms-6.5.13-5-pve# git grep drm_dp_calc_pbn_mode
drivers/gpu/drm/i915/display/intel_dp_mst.c:                    crtc_state-&amp;gt;pbn = drm_dp_calc_pbn_mode(adjusted_mode-&amp;gt;crtc_clock,
drivers/gpu/drm/i915/display/intel_dp_mst.c:                            crtc_state-&amp;gt;pbn = drm_dp_calc_pbn_mode(adjusted_mode-&amp;gt;crtc_clock,
drivers/gpu/drm/i915/display/intel_dp_mst.c:            drm_dp_calc_pbn_mode(mode-&amp;gt;clock, min_bpp, false) &amp;gt; port-&amp;gt;full_pbn) {
drivers/gpu/drm/i915/display/intel_dp_mst.c:            drm_dp_calc_pbn_mode(mode-&amp;gt;clock, min_bpp) &amp;gt; port-&amp;gt;full_pbn) {
root@kagrenac:/usr/src/i915-sriov-dkms-6.5.13-5-pve# grep -R drm_dp_calc_pbn_mode /usr/src/linux-headers-6.5.13-5-pve/
/usr/src/linux-headers-6.5.13-5-pve/Module.symvers:0xa47826e4   drm_dp_calc_pbn_mode    drivers/gpu/drm/display/drm_display_helper      EXPORT_SYMBOL
/usr/src/linux-headers-6.5.13-5-pve/include/drm/display/drm_dp_mst_helper.h:int drm_dp_calc_pbn_mode(int clock, int bpp);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It looks like there are a few uses of &lt;code&gt;drm_dp_calc_pbn_mode&lt;/code&gt;. Some of them have this extra third argument which is apparently not real.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at the file and see wh-&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#if LINUX_VERSION_CODE &amp;lt; KERNEL_VERSION(6,6,14)
        crtc_state-&amp;gt;pbn = drm_dp_calc_pbn_mode(adjusted_mode-&amp;gt;crtc_clock,
                                        dsc ? bpp &amp;lt;&amp;lt; 4 : bpp,
                                        dsc);
#endif
#if LINUX_VERSION_CODE &amp;gt;= KERNEL_VERSION(6,6,14)
        crtc_state-&amp;gt;pbn = drm_dp_calc_pbn_mode(adjusted_mode-&amp;gt;crtc_clock,
                                        dsc ? bpp &amp;lt;&amp;lt; 4 : bpp);
#endif
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright, so these guards are wrong. It might be a PVE thing where a patch was backported or something. I&amp;rsquo;ll just nuke the first
chunk and unconditionally use the &amp;gt;= 6.6.14 stuff.&lt;/p&gt;
&lt;p&gt;I got it to build now.&lt;/p&gt;
&lt;h2 id=&#34;prayer&#34;&gt;Prayer&lt;/h2&gt;
&lt;p&gt;I added &lt;code&gt;i915.enable_guc=3 i915.max_vfs=7&lt;/code&gt; to my kernel cmdline, called update-grub, rebuilt my initramfs, and rebooted with my fingers crossed.&lt;/p&gt;
&lt;p&gt;It booted, but I realized I forgot to do the thing from the Readme to enable the VFs. Rebooted again.&lt;/p&gt;
&lt;p&gt;The kernel module loaded, but no VFs were created. Manually writing to sriov_numvfs just fails.&lt;/p&gt;
&lt;p&gt;Fortunately, &lt;a href=&#34;https://github.com/Upinel/PVE-Intel-vGPU&#34; target=&#34;_blank&#34; &gt;somebody else&lt;/a&gt; also wrote some instructions on how to get this thing working.&lt;/p&gt;
&lt;p&gt;There are only some slight modifications to the configuration and build process here. I was totally
out of my depth but it seemed like this &lt;code&gt;apt install sysfsutils&lt;/code&gt; step might also be important to get the VFs actually
created on boot.&lt;/p&gt;
&lt;p&gt;I rebuilt the module and did the initramfs thing again.&lt;/p&gt;
&lt;p&gt;The kernel logs now presented this error:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[   17.593755] pci 0000:00:02.0: no driver bound to device; cannot configure SR-IOV
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From &lt;code&gt;lspci -k&lt;/code&gt; we can confirm that i915 is not in use on the device, though it &lt;em&gt;did&lt;/em&gt; load.&lt;/p&gt;
&lt;p&gt;If I &lt;code&gt;rmmod&lt;/code&gt; and &lt;code&gt;modprobe&lt;/code&gt; it again, nothing really happened except it logged the same stuff it did on boot:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[  187.619871] Setting dangerous option enable_guc - tainting kernel
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, it still wasn&amp;rsquo;t bound to the device. Trying &lt;code&gt;force_probe&lt;/code&gt; again had no effect.&lt;/p&gt;
&lt;p&gt;I found &lt;a href=&#34;https://www.derekseaman.com/2023/11/proxmox-ve-8-1-windows-11-vgpu-vt-d-passthrough-with-intel-alder-lake.html&#34; target=&#34;_blank&#34; &gt;yet another&lt;/a&gt; post about setting it up,
and still I could find nothing different.&lt;/p&gt;
&lt;h2 id=&#34;deep-in-the-weeds&#34;&gt;Deep in the weeds&lt;/h2&gt;
&lt;p&gt;I did some more googling, and landed on &lt;a href=&#34;https://forum.level1techs.com/t/intel-i915-sr-iov-mode-for-flex-170-proxmox-pve-kernel-6-5/208294&#34; target=&#34;_blank&#34; &gt;something different&lt;/a&gt;.
I unloaded the custom i915, uninstalled it from dkms, and tried building &lt;a href=&#34;https://github.com/intel-gpu/intel-gpu-i915-backports/tree/backport/main&#34; target=&#34;_blank&#34; &gt;this Intel backport thing instead&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It failed with the same &lt;code&gt;drm_dp_calc_pbn_mode&lt;/code&gt; stuff again, so I went back in and made basically those same changes as before.
The module eventually built successfully, then&amp;hellip;. it loaded but did not probe the device, same as before.&lt;/p&gt;
&lt;p&gt;Some &lt;a href=&#34;https://www.phoronix.com/news/Linux-6.11-DRM-Intel-Xe-Next&#34; target=&#34;_blank&#34; &gt;sources&lt;/a&gt; say kernel 6.11 might do the stuff I want it to do. I considered just waiting until it was released.&lt;/p&gt;
&lt;h2 id=&#34;a-year-later-i-tried-again&#34;&gt;A year later, I tried again&lt;/h2&gt;
&lt;p&gt;At first, I had no idea why I had such a hard time with this, since it seems like success came easy for others with 13th gen chips.&lt;/p&gt;
&lt;p&gt;One day, I had a spare couple of hours and decided to update my BIOS. I figured that maybe this would fix SR-IOV too. Support
for 6.8 was available in the DKMS repo now, and I didn&amp;rsquo;t have to do anything weird to get it to build. It still didn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;My motherboard manufacturer suggested to update the Intel ME along with the BIOS update. This was a nightmare, requiring me to
build WinPE boot media in order to run the tool, since there was absolutely no Linux support. However, after doing so,
suddenly SR-IOV was working.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;image.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bask in the glory of my really funny looking fetch due to the quantity of VFs.&lt;/p&gt;
&lt;h2 id=&#34;why&#34;&gt;Why?&lt;/h2&gt;
&lt;p&gt;I have no idea what other firmware lives in the ME image but it seems like it was necessary to enable SR-IOV on my chip.&lt;/p&gt;
&lt;p&gt;I had never considered updating ME before, and nobody on the Internet had advised that it might be necessary for this.&lt;/p&gt;
&lt;p&gt;After this, I followed the other common guidance online to set everything else up and it just worked.&lt;/p&gt;
&lt;p&gt;I guess the moral of this story is to always keep ME up to date? I hate it.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>An ultra-low profile Chiffre: Le Oeuf</title>
      <link>https://lo.calho.st/posts/le-oeuf/</link>
      <pubDate>Fri, 22 Nov 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/le-oeuf/</guid>
      <description>&lt;p&gt;I recently acquired some Kailh PG1425 (&amp;ldquo;X Switches&amp;rdquo;) to experiment with. As my first foray into ultra-low profile,
I designed a riff on the popular Le Chiffre design.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;le-oeuf.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;This keyboard is ridiculously thin, measuring only 1.05 cm at the thickest point, and quite comfortable to type on.&lt;/p&gt;
&lt;p&gt;The open-source design is available &lt;a href=&#34;https://github.com/eggsworks/le-oeuf&#34; target=&#34;_blank&#34; &gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I originally picked up these switches for a different project, but ultimately decided that PG1316S was a better
candidate for that one and needed to find something to do with these. In lieu of any other ideas, I decided to
draw from the Chiffre. Since only 1u caps are available for the X switch, I replaced the 1.25u+2u thumbs with
3x1u.&lt;/p&gt;
&lt;p&gt;As before, I wanted this board to have support for wired and wireless builds. However, to keep it ultra-thin,
I chose a Xiao footprint over the Pro Micro due to its support to be mounted SMT-style. As it has less IO, this
meant I had to use a shift register to drive the matrix. Fortunately,
&lt;a href=&#34;https://zmk.dev/docs/development/hardware-integration/shift-registers&#34; target=&#34;_blank&#34; &gt;ZMK made this super easy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Though this project started out as a mere experiment, Le Oeuf has turned out to be one of my favorite
boards to type on. It has replaced the &lt;a href=&#34;https://lo.calho.st/posts/solanum/&#34; &gt;tented split board&lt;/a&gt; that I have
been dailying for work. As an added bonus, it is ultra-portable.&lt;/p&gt;
&lt;p&gt;The community has shown a lot of interest in this board, and as a result a redesign using PG1316S is in
development. This version will also feature a custom aluminum case for a more premium look and feel. More updates
soon!&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>My latest ergo design: the Solanum</title>
      <link>https://lo.calho.st/posts/solanum/</link>
      <pubDate>Sun, 22 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/solanum/</guid>
      <description>&lt;p&gt;I haven&amp;rsquo;t had much time to devote to my hobbies since moving, but I&amp;rsquo;m quite proud to have finally finished a
project that I&amp;rsquo;ve had in flight for the past several months. This is my latest keyboard design: a 34-key split
that I call the Solanum.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;solanum_2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;In some respects, it represents a return to my roots with many familiar features from the &lt;a href=&#34;https://lo.calho.st/posts/egg58/&#34; &gt;egg58&lt;/a&gt;:
widened spacing, a Pro Micro, TRRS for wired split, and reversible PCBs. However, 34 keys is the smallest
key count for any of my designs yet. I&amp;rsquo;ve been using &lt;a href=&#34;https://lo.calho.st/posts/34-key-layout/&#34; &gt;a layout of this size&lt;/a&gt; for most of the year, but this is the first creation of my own to be designed for it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;solanum_5.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I have integrated some quality-of-life features that I first tested on other boards:
tenting puck support (from the &lt;a href=&#34;https://lo.calho.st/posts/chicklet/&#34; &gt;Chicklet&lt;/a&gt;), and an onboard JST and power switch for
wireless boards (as on the &lt;a href=&#34;https://lo.calho.st/posts/tamago60/&#34; &gt;Tamago60&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I also tried to make this board as easy as possible to hand solder. I stuck to through-hole components (with the exception of the hotswap sockets) and opted for a diodeless design.&lt;/p&gt;
&lt;p&gt;After almost three years of building and testing weird little keyboards, I think I have mostly settled on what I like. While I don&amp;rsquo;t think I&amp;rsquo;m likely to deviate too far from the features I&amp;rsquo;ve described above, I am still willing to try new things.&lt;/p&gt;
&lt;p&gt;In that vein, the Solanum introduces some changes I consider a bit more experimental like a reachy pinky key, more aggressive stagger, and some mild splay. It&amp;rsquo;s too early to say whether these will all stick around in the next revision, but so far it feels like a pretty comfortable keyboard.&lt;/p&gt;
&lt;p&gt;As always, this design is open-source, and can be found &lt;a href=&#34;https://github.com/eggsworks/solanum&#34; target=&#34;_blank&#34; &gt;on my GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>The server has been brought to life</title>
      <link>https://lo.calho.st/posts/new-server-part-2/</link>
      <pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/new-server-part-2/</guid>
      <description>&lt;p&gt;My last post talked about &lt;a href=&#34;https://lo.calho.st/posts/new-server/&#34; &gt;speccing out a server and buying a bunch of parts&lt;/a&gt;. This is a follow up in which I declare success and complain about incompatible parts I had originally selected.&lt;/p&gt;
&lt;p&gt;When I first got started with the build, I was pretty confident, both that it would work and that I would do a good job with cable management.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;PXL_20240524_201446993.MP.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The good news is that it did eventually work. The bad news is it was a huge pain in my ass and took over a week of debugging. As we&amp;rsquo;ll see later, I also stopped being so careful with cable management.&lt;/p&gt;
&lt;p&gt;A series of problems arose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My Intel SSD didn&amp;rsquo;t work&lt;/li&gt;
&lt;li&gt;The Teamgroup NVMe didn&amp;rsquo;t work&lt;/li&gt;
&lt;li&gt;The NICs didn&amp;rsquo;t work&lt;/li&gt;
&lt;li&gt;The PCIe switches didn&amp;rsquo;t work&lt;/li&gt;
&lt;li&gt;I had to 3D print a cooling duct for the Tesla (at least I didn&amp;rsquo;t have to model it myself)&lt;/li&gt;
&lt;li&gt;Thermal throttling (at least I expected this one)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s the final BOM we ended up with:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Qty&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;$/each&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sliger CX2137b&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$199.00&lt;/td&gt;
&lt;td&gt;$199.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arctic F8 PWM Fan&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;td&gt;$24.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asus Pro WS W680-ACE IPMI&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$399.99&lt;/td&gt;
&lt;td&gt;$399.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel i9-13900K&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$490.00&lt;/td&gt;
&lt;td&gt;$490.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynatron Q5 LGA1700 Cooler&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$49.95&lt;/td&gt;
&lt;td&gt;$49.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kingston DDR5-4800 32GB&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$118.75&lt;/td&gt;
&lt;td&gt;$475.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIGABYTE GeForce RTX 4060&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$319.99&lt;/td&gt;
&lt;td&gt;$319.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silverstone FX600&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$174.99&lt;/td&gt;
&lt;td&gt;$174.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVIDIA Tesla P4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Used&lt;/td&gt;
&lt;td&gt;$126.13&lt;/td&gt;
&lt;td&gt;$126.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel DC S3500 480GB&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Used&lt;/td&gt;
&lt;td&gt;On hand&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crucial P3 4TB&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$236.99&lt;/td&gt;
&lt;td&gt;$1421.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startech PEX8M2E2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$162.67&lt;/td&gt;
&lt;td&gt;$162.67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startech PEX4M2E1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$22.93&lt;/td&gt;
&lt;td&gt;$22.93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Noctua NF-A6x25&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$14.95&lt;/td&gt;
&lt;td&gt;$14.95&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Grand total before tax and shipping: $3881.54. Not really too bad.&lt;/p&gt;
&lt;p&gt;Now that we&amp;rsquo;ve established how this build hurt my wallet, let&amp;rsquo;s talk about how it hurt &lt;em&gt;me&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;chapter-1-my-intel-ssd-didnt-work&#34;&gt;Chapter 1: My intel SSD didn&amp;rsquo;t work&lt;/h2&gt;
&lt;p&gt;I installed Debian. I rebooted. The boot device was no longer there. The previous build I used this Intel NVMe in had a big chonky heatsink on it and a huge fan to keep it cool. This build did not really have sufficient cooling.&lt;/p&gt;
&lt;p&gt;The drive kept appearing and disappearing, and I kept fiddling with flags in the UEFI thinking it was just mad about something. But it was just a thermal issue.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t think the controller got completely cooked, as it did appear again later once it cooled down. However, this was a non-starter. I gave up on my all-NVMe ideal and installed some SATA SSDs I had lying around: Intel DC S3500 480GB. I had two of em, so figured why not make it root-on-mirrored-ZFS.&lt;/p&gt;
&lt;p&gt;I installed Debian again.&lt;/p&gt;
&lt;h2 id=&#34;chapter-2-the-teamgroup-nvme-didnt-work&#34;&gt;Chapter 2: The Teamgroup NVMe didn&amp;rsquo;t work&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s quite disappointing to buy 6x4TB of NVMe and not be able to use it. They did appear in the UEFI, but the NVMe behind the PCIe switches on the Glotrends carrier cards just wouldn&amp;rsquo;t show up in Linux. This was a disappointment. The ones installed directly on the motherboard, however, did work.&lt;/p&gt;
&lt;p&gt;I tested the Intel drive I had just removed and found that it &lt;em&gt;did&lt;/em&gt; appear when installed behind the switch. I figure this is a firmware bug on the Team stuff, or in the Realtek controller they use. I sent the drives back and got Crucial P3 4TB as replacements. These ones appeared to work.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;image2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I went ahead and installed Proxmox and set up a big ZFS pool to use for my VMs.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;image.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I started migrating stuff from my old server, then I hit another problem.&lt;/p&gt;
&lt;h2 id=&#34;chapter-3-the-nics-didnt-work&#34;&gt;Chapter 3: The NICs didn&amp;rsquo;t work&lt;/h2&gt;
&lt;p&gt;While I was copying stuff over, I set the MTU to 9000. Then later, I was debugging some stuff on my desk and plugged the server into a 100mbit dumbswitch to get it connected. It did not like this.&lt;/p&gt;
&lt;p&gt;The kernel kept panicking when systemd decided to turn on the NIC. I could boot into a livecd, but not my installed system. I once again started playing with UEFI toggles. I started playing with kernel flags too. It took me &lt;em&gt;way&lt;/em&gt; too long to make the connection that it was because of the jumbo frames and dumbswitch having a bad time with one another.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t feel like running a new cable across the room to the &lt;em&gt;real&lt;/em&gt; switch, but I knew I had more desk-based debugging and testing to do. So, I just mounted / in the livecd and changed the MTU back to 1500.&lt;/p&gt;
&lt;h2 id=&#34;chapter-4-the-pcie-switches-didnt-work&#34;&gt;Chapter 4: The PCIe switches didn&amp;rsquo;t work&lt;/h2&gt;
&lt;p&gt;I noticed my disk utilization frequently dropping to zero during the prior rsync&amp;rsquo;ing. I checked dmesg to see what was going on.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[  988.525697] pcieport 0000:00:1d.0: AER: Multiple Uncorrectable (Non-Fatal) error message received from 0000:12:00.0
[  988.536154] nvme 0000:12:00.0: PCIe Bus Error: severity=Uncorrectable (Non-Fatal), type=Transaction Layer, (Requester ID)
[  988.547103] nvme 0000:12:00.0:   device [c0a9:540a] error status/mask=00004000/00400000
[  988.555098] nvme 0000:12:00.0:    [14] CmpltTO
[  988.560907] pcieport 0000:10:08.0: AER: device recovery successful
[  990.619281] nvme nvme3: I/O tag 204 (d0cc) opcode 0x1 (I/O Cmd) QID 2 timeout, aborting req_op:WRITE(1) size:8192
[  992.667249] nvme nvme3: I/O tag 204 (d0cc) opcode 0x1 (I/O Cmd) QID 2 timeout, reset controller
[  992.688623] nvme nvme3: Abort status: 0x371
[  992.721812] nvme nvme3: Shutdown timeout set to 2 seconds
[  992.933183] nvme nvme3: 8/0/0 default/read/poll queues
[  992.956487] nvme nvme3: Ignoring bogus Namespace Identifiers
[  999.730279] pcieport 0000:00:1d.0: AER: Multiple Uncorrectable (Non-Fatal) error message received from 0000:12:00.0
[  999.740791] nvme 0000:12:00.0: PCIe Bus Error: severity=Uncorrectable (Non-Fatal), type=Transaction Layer, (Requester ID)
[  999.751728] nvme 0000:12:00.0:   device [c0a9:540a] error status/mask=00004000/00400000
[  999.759723] nvme 0000:12:00.0:    [14] CmpltTO
[  999.765495] pcieport 0000:10:08.0: AER: device recovery successful
[ 1000.859080] nvme nvme3: I/O tag 891 (d37b) opcode 0x1 (I/O Cmd) QID 2 timeout, aborting req_op:WRITE(1) size:122880
[ 1002.910082] nvme nvme3: I/O tag 891 (d37b) opcode 0x1 (I/O Cmd) QID 2 timeout, reset controller
[ 1002.932441] nvme nvme3: Abort status: 0x371
[ 1002.961263] nvme nvme3: Shutdown timeout set to 2 seconds
[ 1003.172257] nvme nvme3: 8/0/0 default/read/poll queues
[ 1003.193600] nvme nvme3: Ignoring bogus Namespace Identifiers
[ 1004.128115] pcieport 0000:00:1d.0: AER: Multiple Uncorrectable (Non-Fatal) error message received from 0000:12:00.0
[ 1004.138653] nvme 0000:12:00.0: PCIe Bus Error: severity=Uncorrectable (Non-Fatal), type=Transaction Layer, (Requester ID)
[ 1004.149592] nvme 0000:12:00.0:   device [c0a9:540a] error status/mask=00004000/00400000
[ 1004.157602] nvme 0000:12:00.0:    [14] CmpltTO
[ 1004.259772] pcieport 0000:10:08.0: AER: device recovery successful
[ 1005.211047] nvme nvme3: I/O tag 487 (d1e7) opcode 0x2 (I/O Cmd) QID 3 timeout, aborting req_op:READ(0) size:45056
[ 1006.235030] nvme nvme3: I/O tag 487 (d1e7) opcode 0x2 (I/O Cmd) QID 3 timeout, reset controller
[ 1006.255197] nvme nvme3: Abort status: 0x371
[ 1007.287473] nvme nvme3: Shutdown timeout set to 2 seconds
[ 1007.498696] nvme nvme3: 8/0/0 default/read/poll queues
[ 1007.519380] nvme nvme3: Ignoring bogus Namespace Identifiers
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is bad, awful, and not good. However, it was only occurring for the NVMe on the carrier cards. I sent them back and got a new one with a PLX switch instead of an Asmedia one.&lt;/p&gt;
&lt;p&gt;Before I made that decision, I spent an entire day playing with kernel flags to change NVMe timeouts, turn off APST and ASPM, etc., all to no avail.&lt;/p&gt;
&lt;p&gt;Since I had ditched root-on-NVMe, I had a spare slot on the board, so I now only needed one card with a switch, and one that was basically just a dumb adapter.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t really go out of my way to optimize this (e.g., the PCIe switch carrying two of the NVMe is installed in a x4 slot instead of a x8) but it gets pretty decent performance.&lt;/p&gt;
&lt;p&gt;A quick test in fio returned some surprisingly round numbers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read: 7500MiB/s, 30k IOPS&lt;/li&gt;
&lt;li&gt;Write: 5000MiB/s, 20k IOPS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is way more than good enough for my use case.&lt;/p&gt;
&lt;h2 id=&#34;chapter-5-i-had-to-3d-print-a-cooling-duct-for-the-tesla&#34;&gt;Chapter 5: I had to 3D print a cooling duct for the Tesla&lt;/h2&gt;
&lt;p&gt;The Tesla P4 is designed for &lt;em&gt;real&lt;/em&gt; servers that have a bunch of beefy fans to push a ton of air through it. This server is not one of those.&lt;/p&gt;
&lt;p&gt;I ran into a bunch of problems with my printer that I don&amp;rsquo;t feel like elaborating on, but ultimately I got a part made.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tesla_fan.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s ugly but it works.&lt;/p&gt;
&lt;p&gt;Aside: This Tesla was sold as open box, but I could see evidence of a really janky refurb job, like poorly-cut thermal pads sticking out under the backplate. I decided to take it apart and redo all the pads and paste myself. Fortunately I did not brick the card.&lt;/p&gt;
&lt;h2 id=&#34;chapter-6-thermal-throttling&#34;&gt;Chapter 6: Thermal throttling&lt;/h2&gt;
&lt;p&gt;I knew I would have thermal issues with the 13900K, since I had originally specced a run-of-the-mill 13900.&lt;/p&gt;
&lt;p&gt;Ultimately, I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disable hyperthreading (not sure if this was really necessary)&lt;/li&gt;
&lt;li&gt;Set a max boost clock of 4.3GHz&lt;/li&gt;
&lt;li&gt;Set lower power caps and slightly undervolted&lt;/li&gt;
&lt;li&gt;Disabled the intel_pstate kernel module and used cpupower to load the conservative governor and ensure the lowest-power states were enabled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This got me down to an idle package temp (with VMs idling) of 35C, at 98W total system power draw. It briefly hits 90C and pulls 330W during &lt;code&gt;stress -c 24&lt;/code&gt;, but then once the &amp;ldquo;long duration&amp;rdquo; power cap kicks in it drops to 80C/265W.&lt;/p&gt;
&lt;p&gt;While we&amp;rsquo;re here, I&amp;rsquo;ll also mention that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Tesla pulls 25W and sits at 60C while idling with a model loaded,&lt;/li&gt;
&lt;li&gt;&amp;hellip;and hits 65W/75C during inferencing.&lt;/li&gt;
&lt;li&gt;I set a 75% power cap and 70C thermal limit on the 4090 and it idles at 40C,&lt;/li&gt;
&lt;li&gt;&amp;hellip;and hit 69C during a benchmark in Borderlands 3 at 1080p60 (in which it achieved an average of 58.85fps with a 17ms frame time).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I find this perfectly acceptable.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In the end, the inside of the thing looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;guts.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;My awful cable management job is fortunately hidden behind a rather pleasant facade.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;PXL_20240601_020302480.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ignore the mess on my desk and the fact that the lid wasn&amp;rsquo;t closed in that picture.&lt;/p&gt;
&lt;p&gt;I now have a nice 2U machine to replace &lt;em&gt;both&lt;/em&gt; my ancient HP workstation tower &lt;em&gt;and&lt;/em&gt; my old Synology NAS.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>It&#39;s time for a new server</title>
      <link>https://lo.calho.st/posts/new-server/</link>
      <pubDate>Sun, 12 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/new-server/</guid>
      <description>&lt;h2 id=&#34;setting-out-to-waste-some-money&#34;&gt;Setting out to waste some money&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s only been about two years since I built &lt;a href=&#34;https://lo.calho.st/posts/gaming-vm/&#34; &gt;an atrocity&lt;/a&gt; of a VM server in a decade-old workstation box. It still gets the job done, but there is empty space to fill in my homelab cabinet so it&amp;rsquo;s time for an upgrade. In the interest of killing two birds with one stone, I&amp;rsquo;d like it to replace my old Synology NAS too, which is also perfectly adequate despite its age.&lt;/p&gt;
&lt;p&gt;The constraints and use case of this system make it a pretty challenging build to spec out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two GPUs: one for the gaming VM, one for an AI VM. Both must have at least PCIe4 x8 bandwidth.&lt;/li&gt;
&lt;li&gt;Shallow 2u chassis: so it fits in my cabinet. I&amp;rsquo;ve already been though too many different sized ones.&lt;/li&gt;
&lt;li&gt;At least 16 cores. Ideally, more.&lt;/li&gt;
&lt;li&gt;At least 128 GB of RAM.&lt;/li&gt;
&lt;li&gt;ECC support. This limits us to server/workstation SKUs and only the most recent consumer ones.&lt;/li&gt;
&lt;li&gt;Room for enough SSDs to get at least 10 TB of usable capacity. I&amp;rsquo;ve currently got about 5 TB used so this gives me decent headroom.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s start picking some parts.&lt;/p&gt;
&lt;h2 id=&#34;case-and-gpus&#34;&gt;Case and GPUs&lt;/h2&gt;
&lt;p&gt;These choices end up being intrinsically linked, since the shallow cabinet ends up being the biggest problem. Nothing with more than about 14&amp;quot; depth will really fit. I considered 3u and 4u chassis as well, but somehow 2u ended up working out the best.&lt;/p&gt;
&lt;p&gt;While 3u ostensibly supports a full-height card, consumer GPUs don&amp;rsquo;t really fit because they tend to extend a bit above the PCI bracket. You&amp;rsquo;d think 4u avoids this problem, but the only 4u case I can find that is 14&amp;quot; deep or less has the motherboard on an elevated tray that makes it no better than a 3u.&lt;/p&gt;
&lt;p&gt;There are way more options available for 2u cases, but this constrains me to half-height GPUs. There aren&amp;rsquo;t many options with risers to circumvent this, and none of them are good. There also aren&amp;rsquo;t many good half-height GPUs. Thus, it was time to lock in our first two part selections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gigabyte GeForce RTX 4060 OC Low Profile 8G, for the gaming VM&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Nvidia RTX A2000 12GB, for the AI VM&lt;/del&gt; (&lt;strong&gt;Update&lt;/strong&gt;: I got a P4 instead. It was cheaper.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The 4060 is basically the only &amp;ldquo;gaming&amp;rdquo; GPU available in this form factor that isn&amp;rsquo;t a decade old. I acknowledge that the A2000 is a weirder choice here, but ultimately I chose it because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a decent amount of RAM.&lt;/li&gt;
&lt;li&gt;&lt;del&gt;It&amp;rsquo;s way cheaper than any of the half-height Teslas.&lt;/del&gt; (&lt;strong&gt;Update&lt;/strong&gt;: Actually, it wasn&amp;rsquo;t.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To fit both of these dual-slot cards in a 2u case, now one of two things must be true:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It takes a full ATX motherboard, so there are more than 4 bays, or&lt;/li&gt;
&lt;li&gt;I find a mATX board that actually puts two (physically) x16 slots in the 1st and 3rd positions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After hours of searching, I could not really find any mATX board that worked here, so I
settled for the only shallow 2u full-ATX case I could find, locking in our third part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sliger CX2137b&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s only 13&amp;quot; deep, so no problem there. It has 7 PCI bays and takes a flexATX PSU. Looks pretty decent too.&lt;/p&gt;
&lt;p&gt;Only one issue. I originally planned to find a case with room for two 5.25&amp;quot; bays so I could put a backplane in them and fill it with 2.5&amp;quot; SSDs. This case doesn&amp;rsquo;t have the 5.25&amp;quot;, so I had to find another option. I could fill the interior of the case with SATA SSDs, but it only really fits five. I&amp;rsquo;d end up with a 4-member RAID-10, a non redundant boot disk, and a huge mess of cables. Time to figure out how to solve this problem.&lt;/p&gt;
&lt;h2 id=&#34;motherboard&#34;&gt;Motherboard&lt;/h2&gt;
&lt;p&gt;At this point, I decided to commit to slapping a bunch of NVMe in it. Thus, a new constraint has been introduced: we need a buttload of M.2, or at least lots of PCIe slots we can stick risers in.&lt;/p&gt;
&lt;p&gt;After looking at basically every motherboard SKU produced for the latest 3 or 4 generations of both AMD and Intel chips, I figured out that there is basically only one chipset that will get the job done: Intel W680.&lt;/p&gt;
&lt;p&gt;There aren&amp;rsquo;t a ton of boards with these chips in them, since it targets the pretty-niche part of the workstation segment that isn&amp;rsquo;t on Xeons. However, the workstation application is ultimately the consideration that makes this the best chip for the job: they are all intended to support multiple GPUs.&lt;/p&gt;
&lt;p&gt;All of the W680 boards I found let you run the 16 PCIe Gen5 lanes off the CPU as either a single x16 or two x8&amp;rsquo;s. We&amp;rsquo;ll be choosing the latter option.&lt;/p&gt;
&lt;p&gt;We must now compare boards for their other features, particularly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many ethernet ports does it have?&lt;/li&gt;
&lt;li&gt;Does the firmware actually support ECC?&lt;/li&gt;
&lt;li&gt;How much storage can I attach to it?&lt;/li&gt;
&lt;li&gt;Any extra goodies like IPMI?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, I had three serious candidates: one from Supermicro (X13SAE-F), one from Asus (Pro WS W680-ACE IPMI), and one from Gigabyte (MW34-SP0).&lt;/p&gt;
&lt;p&gt;Though I&amp;rsquo;m a pretty big Gigabyte stan, unfortunately there&amp;rsquo;s enough FUD on the internet to make me wary that they flaked pretty hard on the ECC support for this one. It also only has one 2.5GbE port - which is enough bandwidth, but I&amp;rsquo;ve only got gigabit switches so was betting on two 1GbE so I could LACP them. With two strikes against it, I decided to eliminate it from the running.&lt;/p&gt;
&lt;p&gt;Both had IPMI (though the Asus is weird, we&amp;rsquo;ll get to that in a second) and ECC support that
apparently works. The decision ultimately came down to the Asus being $100 cheaper and having
two 2.5GbE ports instead of just one.&lt;/p&gt;
&lt;p&gt;Indeed, we have finally locked in our fourth part:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Asus Pro WS W680-ACE IPMI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It has a PCIe layout that looks something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU
&lt;ul&gt;
&lt;li&gt;PCIe 5.0 x8 (in a x16 slot)&lt;/li&gt;
&lt;li&gt;PCIe 5.0 x8 (in a x16 slot)&lt;/li&gt;
&lt;li&gt;PCIe 4.0 x4 M.2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PCH
&lt;ul&gt;
&lt;li&gt;2x PCIe 3.0 x4 (in a x16 slot)&lt;/li&gt;
&lt;li&gt;PCIe 3.0 x1 (for the IPMI card)&lt;/li&gt;
&lt;li&gt;2x PCIe 4.0 x4 M.2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yeah. That&amp;rsquo;s the weird part - the IPMI is on an expansion card. It seems like the motherboard does have the BMC on it, but this card is needed to give it ethernet and VGA to turn it into a full-fledged IPMI. Odd choice, but whatever. Fortunately it doesn&amp;rsquo;t take up any of the PCIe I actually want to use. Still plenty of PCIe lanes for a bunch of NVMe.&lt;/p&gt;
&lt;h2 id=&#34;storage&#34;&gt;Storage&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Don&amp;rsquo;t buy the parts listed in this section. They didn&amp;rsquo;t work. Refer to my &lt;a href=&#34;https://lo.calho.st/posts/new-server-part-2/&#34; &gt;new post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;We&amp;rsquo;re going to jam 6 M.2 drives in this thing, using the two onboard M.2&amp;rsquo;s attached to the PCH, and two carrier cards to add two more in each of the x4 slots. Since the slots are x4, this means we need active cards:&lt;/del&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;2x GLOTRENDS PA20 Dual M.2 NVMe to PCIe 3.0 X4 Adapter&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;del&gt;After considering several options, the smart move is to just make the bulk of the storage something cheap, reliable, and high density, even if it doesn&amp;rsquo;t have the highest performance:&lt;/del&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;6x TEAMGROUP MP34 4TB&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;del&gt;We were limited to Gen3 speeds and bottlenecked through those switches anyway, so the only real loss here is in random IOPS. Maybe we can make up for it with the 3-way stripe or the mirroring.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;We&amp;rsquo;ll run some random spare SSD I have around for boot:&lt;/del&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;del&gt;Intel DC P4511 1 TB&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;power-supply&#34;&gt;Power supply&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re stuck with a flex PSU thanks to the case we had to choose. Let&amp;rsquo;s just pick the biggest one available from a respectable brand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Silverstone FX600 Platinum&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;cpu&#34;&gt;CPU&lt;/h2&gt;
&lt;p&gt;We want a CPU that supports ECC and has a ton of cores. However, we&amp;rsquo;re pretty limited in terms of power. After doing some math, I figured I can afford a 65W TDP CPU. This means an i9-13900 is the best choice. However, it so happens that the -K is cheaper right now. I&amp;rsquo;ll get one of those and clock it down to non-K speeds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel i9-13900K&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not opting to pay extra for a 14900, since it doesn&amp;rsquo;t really seem to perform any better.&lt;/p&gt;
&lt;h2 id=&#34;ram&#34;&gt;RAM&lt;/h2&gt;
&lt;p&gt;We need 4x 32GB DDR5 ECC UDIMMs. There is only one SKU on the QVL that I can actually seem to purchase right now, and I don&amp;rsquo;t feel like playing games:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kingston DDR5-4800 32GB ECC&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;adding-it-all-up&#34;&gt;Adding it all up&lt;/h2&gt;
&lt;p&gt;The last touches are fans and a CPU cooler. Let&amp;rsquo;s find those, recap the BOM and add up some numbers. I planned to get a bunch of stuff from eBay to save money, but it turns out that most of these parts aren&amp;rsquo;t exactly plentiful on the secondary market.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Qty&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;$/each&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sliger CX2137b&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$199.00&lt;/td&gt;
&lt;td&gt;$199.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arctic F8 PWM Fan&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;td&gt;$24.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asus Pro WS W680-ACE IPMI&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$399.99&lt;/td&gt;
&lt;td&gt;$399.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel i9-13900K&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$490.00&lt;/td&gt;
&lt;td&gt;$490.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynatron Q5 LGA1700 Cooler&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$49.95&lt;/td&gt;
&lt;td&gt;$49.95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kingston DDR5-4800 32GB&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$118.75&lt;/td&gt;
&lt;td&gt;$475.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIGABYTE GeForce RTX 4060&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$319.99&lt;/td&gt;
&lt;td&gt;$319.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silverstone FX600&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;$174.99&lt;/td&gt;
&lt;td&gt;$174.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;NVIDIA RTX A2000 12 GB ECC&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;1&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;Used&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$450.00&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$450.00&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;Intel P4511 1TB&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;1&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;Used&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;On hand&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$0.00&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;GLOTRENDS PA20&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;2&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;New&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$97.99&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$195.98&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;TEAMGROUP MP34 4TB&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;6&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;New&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$225.99&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;$903.96&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I ended up deviating from my original plan. An explanation and new BOM is in &lt;a href=&#34;https://lo.calho.st/posts/new-server-part-2/&#34; &gt;this new post&lt;/a&gt;. tl;dr: I got a cheaper GPU, the Team NVMe was buggy, my Intel NVMe failed, and the PCIe switches sucked.&lt;/p&gt;
&lt;p&gt;Everything&amp;rsquo;s been ordered except the A2000, which I&amp;rsquo;m going to put off for a little bit to make sure everything else works first.&lt;/p&gt;
&lt;p&gt;[&lt;em&gt;a previous version of this post had a pie chart of the cost breakdown here, but I didn&amp;rsquo;t feel like updating it after changing the BOM, so it has been removed&lt;/em&gt;]&lt;/p&gt;
&lt;p&gt;My power budget is also pretty tight, so let&amp;rsquo;s see what kind of shape we&amp;rsquo;re in. I could not find actual power data for some of these parts, so it&amp;rsquo;s estimated based on similar ones (or wild guesses).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Power&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intel i9-13900K (throttled)&lt;/td&gt;
&lt;td&gt;219 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIGABYTE GeForce RTX 4060&lt;/td&gt;
&lt;td&gt;115 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;NVIDIA RTX A2000 12 GB&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;70 W&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;6x TEAMGROUP MP34 4TB&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;48 W&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asus Pro WS W680-ACE IPMI&lt;/td&gt;
&lt;td&gt;30 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4x Kingston DDR5-4800 32GB&lt;/td&gt;
&lt;td&gt;20 W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;2x GLOTRENDS PA20&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;10 W&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;del&gt;Intel P4511 1TB&lt;/del&gt;&lt;/td&gt;
&lt;td&gt;&lt;del&gt;8 W&lt;/del&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Max draw looks like 520 W, so we still have a decent amount of headroom with the 600 W PSU. If I end up running into any problems I can just further limit the CPU or one of the GPUs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I changed the BOM, but I don&amp;rsquo;t feel like finding new power numbers. It should be basically the same.&lt;/p&gt;
&lt;h2 id=&#34;now-we-wait&#34;&gt;Now we wait&lt;/h2&gt;
&lt;p&gt;The parts have yet to arrive. Will update with a post about my struggles when they do.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Adapting to just 34 keys</title>
      <link>https://lo.calho.st/posts/34-key-layout/</link>
      <pubDate>Fri, 03 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/34-key-layout/</guid>
      <description>&lt;p&gt;A while back I posted about my &lt;a href=&#34;https://lo.calho.st/posts/chicklet/&#34; &gt;38-key layout&lt;/a&gt;,
and mentioned that the 34-key form factor is quite a bit more common. As it happens,
I&amp;rsquo;ve been dailying 34-key layouts for the past few months and wanted to share the layout
I settled on.&lt;/p&gt;
&lt;p&gt;There are a lot of hyperoptimized layouts for 34-key boards floating around, but they are
not really easy to adapt to when coming from a larger board. My layout developed pretty much
organically over the past 4-5 months - the starting point was basically a 60%, and I moved
stuff around in a way that felt intuitive to me.&lt;/p&gt;
&lt;p&gt;The main tenets of this layout are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Home row mods&lt;/li&gt;
&lt;li&gt;Only two layer-tap keys, on the inner thumbs&lt;/li&gt;
&lt;li&gt;Most symbols can be reached with only one hold, though a few need two&lt;/li&gt;
&lt;li&gt;Common function keys (F1-F5) can be reached with only one held key&lt;/li&gt;
&lt;li&gt;Common shortcuts (Cmd-C, Cmd-V, Cmd-X) can be performed on one hand&lt;/li&gt;
&lt;li&gt;No combos besides mod-tap and layer-tap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without further ado, here it is:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;my_keymap.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The visualization was produced with &lt;a href=&#34;https://caksoylar.github.io/keymap-drawer?keymap_yaml=H4sIAAAAAAAC_5WTW3fSQBDH3_spxnhB7WALFS853qCwbbU3BdRKa0xhKxySkCZB5WB87LOXb9hP4myyuwmp1uPLZPe3M5N__jtx7NlkGplLAKfu2Brz2fHEDgYmnPAgGIUr4WfOfXnopKmwXT_Y63as0HdGkbX2pWZVl-iIB6Ho0rBDLp4AZei9RHiD0EJ4hdBBOEDoImwh7CHsH6mk-dCE9nB0EiFQ83qMIMh6FDgJaCvgDpJ9U-7rTlrAaL-BsLlInxeqXhTbbkuQvdkoGfGCKJ37rtDsbUHCOu1fIzQQdhF26Ex0QwMhpOcjI5bkjiRPNFmR5Gn25oT3JJ8bscJC6sxFSCzx7T7Pn-zan9KTlhfxQJ00xqG4OirTF1JBqCKsIdxFqCHcQ7iP8ADhIcJq9u1CwhWSoOxRcp-lTPii0FWJ3IEi11JC3hAxrtPOeC_CDRFui3BThFuGfh8jVYxkMdLFSBiryVYfpA3ftGGllJSMkiJlmWPpnMeSLGtyeCjR14LPR5LHmc_RzOcmDLkzyBvMpl5f7ZvcoRVZrl1thX0a6I9dH-H87IdYNj2aiImzjOBNHUfFLS9Uy_0gavc9bUEKz8--i_BLhJ9Jg7LKn2tL55ndubvQV1VouTlxOf2BQmDLo-ndmUZ8UVQxqgZinfOqYx8n95p34g9mpeaIJPOy62U0eYxGj9HsMRo-VlnNjYMoqFQvl_l34b3_K_vnt18ciItIFPwG_mVEokoFAAA%3D&#34; target=&#34;_blank&#34; &gt;keymap-drawer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s drawn here on a Sweep, but I&amp;rsquo;ve got it running on my Claudia and Corne too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;chiffre.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;corne.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;(I removed the tucky thumb keys since taking that photo of the Corne.)&lt;/p&gt;
&lt;p&gt;These keymaps are available in my &lt;a href=&#34;https://github.com/tmick0/qmk_firmware/blob/tjmick-claudia/keyboards/claudia/keymaps/default/keymap.c&#34; target=&#34;_blank&#34; &gt;QMK fork&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Expanding LVM PVs and LVs</title>
      <link>https://lo.calho.st/posts/extend-lvm/</link>
      <pubDate>Fri, 26 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/extend-lvm/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m writing this down because I keep having to expand disks in PVE, but can&amp;rsquo;t remember
the commands for the guest and keep having to google it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Extend the disk in PVE by however much&lt;/li&gt;
&lt;li&gt;Use fdisk to expand (really, delete then recreate) the partition
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# fdisk /dev/vda
Command: p
Disk /dev/vda: 192 GiB, 206158430208 bytes, 402653184 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 2283D992-048D-407F-A13C-63AFDA9CC224

Device       Start       End   Sectors  Size Type
/dev/vda1     2048   1050623   1048576  512M EFI System
/dev/vda2  1050624   2050047    999424  488M Linux filesystem
/dev/vda3  2050048 201324543 199274496   95G Linux filesystem

Command: d
Partition number (1-3, default 3):

Partition 3 has been deleted.

Command: n
Partition number (3-128, default 3):
First sector (2050048-402653150, default 2050048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2050048-402653150, default 402651135):

Created a new partition 3 of type &amp;#39;Linux filesystem&amp;#39; and of size 191 GiB.
Partition #3 contains a LVM2_member signature.

Do you want to remove the signature? [Y]es/[N]o: n

Command:  w
The partition table has been altered.
Syncing disks.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Extend the PV
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# pvresize /dev/vda3
Physical volume &amp;#34;/dev/vda3&amp;#34; changed
1 physical volume(s) resized or updated / 0 physical volume(s) not resized
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Extend the LV
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# lvresize -l +100%FREE /dev/mapper/vaermina--vg-root
Size of logical volume vaermina-vg/root changed from &amp;lt;95.02 GiB (24325 extents) to &amp;lt;191.02 GiB (48901 extents).
Logical volume vaermina-vg/root successfully resized.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Extend the ext4 partition
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# resize2fs /dev/mapper/vaermina--vg-root
resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/mapper/vaermina--vg-root is mounted on /; on-line resizing required
old_desc_blocks = 12, new_desc_blocks = 24
The filesystem on /dev/mapper/vaermina--vg-root is now 50074624 (4k) blocks long.
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Stick a trackball on your keyboard</title>
      <link>https://lo.calho.st/posts/corne-trackball/</link>
      <pubDate>Sun, 14 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/corne-trackball/</guid>
      <description>&lt;p&gt;Despite having designed several of my own ergonomic columnar split keyboards, I have been
daily driving my Corne (crkbd) for the past several months. I removed the tucked-most
thumb keys and outer pinky column and thus am using it with a 34-key layout.&lt;/p&gt;
&lt;p&gt;I usually set up with my mouse (a Logitech MX Vertical) in between the two halves of the
board. Recently, I found that the repetitive motion back and forth between the keyboard
and mouse to be somewhat irritating. I figured it was time for an integrated pointing device.&lt;/p&gt;
&lt;p&gt;I had &lt;a href=&#34;https://lo.calho.st/posts/tamago60/&#34; &gt;experimented in the past&lt;/a&gt; with embedding joysticks, and later
Cirque trackpads in keyboards, but ultimately was not a huge fan of how they felt to use. I&amp;rsquo;ve never used a trackball before,
but decided to give the Pimoroni trackball a shot since it was sort of closer in operation to a trackpoint, which
I don&amp;rsquo;t mind using on my laptop.&lt;/p&gt;
&lt;p&gt;Fortunately, there was already &lt;a href=&#34;https://github.com/qmk/qmk_firmware/blob/master/docs/feature_pointing_device.md#pimoroni-trackball&#34; target=&#34;_blank&#34; &gt;support for it in QMK&lt;/a&gt;,
and an esteemed member of the ergo keyboard community had posted
&lt;a href=&#34;https://www.printables.com/model/79765-corne-crkbd-printable-oled-covers-including-pimoro/files&#34; target=&#34;_blank&#34; &gt;an STL file&lt;/a&gt;
to embed the module into the Corne&amp;rsquo;s MCU cover.&lt;/p&gt;
&lt;p&gt;One poorly-calibrated print and a few bodge wires later, and my Corne had a trackball on it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;corne-trackball.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I ended up writing some &lt;a href=&#34;https://github.com/tmick0/qmk_firmware/blob/tmick0-crkbd/keyboards/crkbd/keymaps/tmick0/keymap.c&#34; target=&#34;_blank&#34; &gt;firmware logic&lt;/a&gt;
to use an auto mouse layer, with a few cheap tricks to give me a hold-to-scroll modifier. For
fun, I also used the RGBW LED in the trackball to indicate the active layer.&lt;/p&gt;
&lt;p&gt;A few hours of testing later, and the trackball feels pretty natural to use, and is much less
annoying to flick compared to reaching over to grab a mouse.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Audio controls in Home Assistant using MQTT</title>
      <link>https://lo.calho.st/posts/mqtt-amp-control/</link>
      <pubDate>Wed, 03 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/mqtt-amp-control/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://lo.calho.st/posts/cec-to-rs-232/&#34; &gt;a previous post&lt;/a&gt;, I shared some software I wrote
to get my amp to listen to power and volume commands from my TV. Now, I&amp;rsquo;ve added the
ability to control it from MQTT as well. It integrates with Home Assistant, so I can
have a nice view that essentially serves as a remote control. It&amp;rsquo;s quite handy for
adjusting volume if I&amp;rsquo;ve put some music on then walked away.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;homeassistant-view.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a bit limited right now since the MQTT feature is just bolted onto the
driver that was intended for the CEC use case, but there&amp;rsquo;s definitely room
to expand it in the future.&lt;/p&gt;
&lt;p&gt;More info on the project is available &lt;a href=&#34;https://github.com/tmick0/cec2rs232&#34; target=&#34;_blank&#34; &gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>It&#39;s really easy to make a bike electric</title>
      <link>https://lo.calho.st/posts/ebike/</link>
      <pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/ebike/</guid>
      <description>&lt;p&gt;Over the weekend, I converted my seldom-used mountain bike to an e-bike.&lt;/p&gt;
&lt;p&gt;I never used my MTB much to begin with, but since moving it is even harder
to find the time (or a place) for a ride. However, the hills here are pretty
steep and this city&amp;rsquo;s roads seem adversarial to cars. It&amp;rsquo;s a great use case
for a utilitarian e-bike, but I didn&amp;rsquo;t want to let my old frame go to waste.&lt;/p&gt;
&lt;p&gt;I chose to purchase a mid-drive conversion instead of a hub motor kit. My original
thinking was that retaining use of the bike&amp;rsquo;s existing transmission would be
beneficial. Starting with a MTB not just granted the comfort of a front suspension to
lessen the blow of all the potholes, but also meant I&amp;rsquo;d have a cogset with a good
selection of low gears for uphill rides.&lt;/p&gt;
&lt;p&gt;Contrary to my expectations, it turns out that many of my gears became useless. The
CYC Photon, at least in my 52V setup, is plenty capable of starting up a steep hill in
even my 4th (28T) or 5th (24T) gear (with a 38T chainring). Even the upper gears are
too close for them all to be super useful under assist, and my top speed (in 11T) is quite
limited. A more compact cassette (and probably a larger chainring) are in my future.&lt;/p&gt;
&lt;p&gt;Downsizing with this move meant I had to give up my project car, but it seems like I&amp;rsquo;ve
found something else to tinker with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;bike.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Introducing another split keyboard: the Chicklet</title>
      <link>https://lo.calho.st/posts/chicklet/</link>
      <pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/chicklet/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s time for another keyboard post! My latest creation is a 38-key split and today we&amp;rsquo;ll be talking about its design and development.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;chicklet.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;layout&#34;&gt;Layout&lt;/h2&gt;
&lt;p&gt;Key counts for split keyboards are essentially bimodal - there many around 60%, and again many with just 34 keys. A 60%ish split presents ease of use, as it can quickly be adapted to from a standard keyboard. The 34-key split meanwhile begins to manifest the ideal of 1DFH, or one distance from home &amp;mdash; meaning that each key is no further than one position from home position, minimizing the amount of finger travel while typing. Each hand of a 34-key split will typically have a 3x5 body and two thumb keys.&lt;/p&gt;
&lt;p&gt;The 38-key form factor is a bit less popular and doesn&amp;rsquo;t seem to be well represented in the hobby. It has just a few more keys than 34, but I have found that those extra keys provide quite a bit of utility. It strays only slightly from 1DFH, but allows more keycodes to be accessible with fewer layers.&lt;/p&gt;
&lt;p&gt;Consider the following groups of keycodes which you find represented on a 100% keyboard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alpha &lt;code&gt;A-Z&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Numerals &lt;code&gt;1-0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Symbols &lt;code&gt;-=[];&#39;,./`\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Space, tab, enter, and backspace&lt;/li&gt;
&lt;li&gt;Shifted numeral symbols &lt;code&gt;!@#$%^&amp;amp;*()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Modifiers (&lt;code&gt;shift, ctrl, caps, alt, super&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Navigation (&lt;code&gt;up, left, right, down, pgup, pgdn, home, end, del, esc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Functions &lt;code&gt;F1-F12&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can consider the shifted numerals here as already being on a de-facto layer, even on a 100% keyboard, as you must hold shift to access them. On a 60%, the last two groups are moved to layers: function keys are accessed through Fn, and navigation keys are often on the Fn layer as well. On a 40%, the numerals themselves are removed from the default layer too. Often, you now see the &amp;ldquo;navigation&amp;rdquo; and &amp;ldquo;numeral-symbol&amp;rdquo; groups being split into two separate layers. On most 30% boards, you now throw away dedicated keys for modifiers, and instead use a tap-hold capability to have them share keys with alphas. Often, layers will also be accessed via tap-hold on keys shared with codes like space or enter.&lt;/p&gt;
&lt;p&gt;Once you get down to 30%, you find yourself having several raised layers. For example, the keymap I use for 34-key boards has three: numbers and symbols, navigation, and functions. There are many ways to access layers, but the most common is momentary (held) layer keys located in the thumb cluster. This is great if you have multiple thumb keys, however I prefer to use only one per hand.&lt;/p&gt;
&lt;p&gt;With just one key per hand, we become somewhat limited in how many layers we can have. On my 34-key keymap, the left thumb key activates the num-sym layer, the right activates nav, and both simultaneously activates functions. However, it would be nice to remove the need to press both at the same time to access that layer &amp;ndash; and I did not want to add another layer-tap key.&lt;/p&gt;
&lt;p&gt;This is the problem solved by going up to 38 keys: numbers, symbols that don&amp;rsquo;t fit on the main layer, and function keycodes can all fit together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;keymap.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Another idiosyncrasy of this layout is that the three keys below the main block are inline with body columns rather than being part of the thumb cluster. This is another personal preference of mine &amp;mdash; I deviate from 1DFH slightly here, but find that the mobility of those three fingers allows easier access to these keys than my thumb would.&lt;/p&gt;
&lt;h2 id=&#34;hardware&#34;&gt;Hardware&lt;/h2&gt;
&lt;p&gt;Another goal for this board was to make building it relatively inexpensive. I used a Seeed XIAO microcontroller and two PCA9555 I2C I/O expanders. Strictly speaking, the primary (left) side of the keyboard did not need the I/O chip but could have been matrixed directly on the XIAO &amp;ndash; however, I wanted to have access to the interrupt signal from the peripheral PCA9555 so I could build a wireless version of this board which does not need to constantly scan. The XIAO did not have the I/O to spare, so I added another chip on that side, too.&lt;/p&gt;
&lt;p&gt;The addition of two PCA9555s at around $1 each still makes this cheaper than a dual-XIAO split board. It also makes a wired split link possible while retaining the possibility of using BLE to connect to a computer - which is much more battery friendly than a dual-MCU split can be given the current limitations of ZMK.&lt;/p&gt;
&lt;p&gt;Since this was meant to be a more budget oriented board, I omitted RGB this time around. However, I retained hotswap sockets to make assembly quick and easy. I also added features to allow attaching a tenting puck, so it can be attached to a mini tripod for customizable positioning.&lt;/p&gt;
&lt;p&gt;One final hardware feature worth mentioning here is a set of pads designed to enable a modular peripheral system. I would like to be able to add extra features like OLEDs and trackpads, but wanted to do so in a way that would not require soldering. I placed four pads, exposing power and I2C, and intend the modules to use pogo pins to connect to these.&lt;/p&gt;
&lt;p&gt;I had all the SMT components, except the XIAO, assembled overseas, and the PCBs and switchplates arrived in their panels.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;panel.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;firmware&#34;&gt;Firmware&lt;/h2&gt;
&lt;p&gt;For now, I have only done the work for QMK but intend to set up ZMK in the future. While trying to get the firmware working, I noticed one small error in the design. Fortunately, it ended up not being a blocker. The PCA9555 expects logic-low on its inputs, however I set up the diodes for logic-high. This only meant I had to swap the roles of pins, i.e. between rows and columns, in firmware.&lt;/p&gt;
&lt;p&gt;The firmware is available in the &lt;a href=&#34;https://github.com/eggsworks/qmk_firmware/tree/chicklet-devel/keyboards/eggsworks/chicklet&#34; target=&#34;_blank&#34; &gt;eggsworks QMK fork&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;build&#34;&gt;Build&lt;/h2&gt;
&lt;p&gt;The full build is described on my &lt;a href=&#34;https://docs.eggs.works/docs/build-guides/chicklet/&#34; target=&#34;_blank&#34; &gt;documentation site&lt;/a&gt;. In summary, the XIAO needs to be soldered to the board, one only needs to attach the switchplate then install switches and keycaps before plugging it in and flashing firmware.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;chicklet-2.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The board is &lt;a href=&#34;https://github.com/eggsworks/chicklet&#34; target=&#34;_blank&#34; &gt;open source&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Blue tape or Kapton? Why not both?</title>
      <link>https://lo.calho.st/posts/blue-tape-kapton-why-not-both/</link>
      <pubDate>Fri, 20 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/blue-tape-kapton-why-not-both/</guid>
      <description>&lt;p&gt;In the 3D printing community, there is endless debate about the best bed surface. In any case, blue (painter&amp;rsquo;s) tape and Kapton (polyimide) tape are two top contenders for many materials.&lt;/p&gt;
&lt;p&gt;In my laziness, I have discovered that the best bed surface is, indeed, both.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;zoidberg.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;How it works is quite simple: you put the blue tape on the bed, then you put the Kap tape over it. The painter&amp;rsquo;s tape provides a better surface to apply the Kapton to than the raw bed. It is far less prone to wrinkling or bubbling during application this way.&lt;/p&gt;
&lt;p&gt;I developed this technique by accident when I wanted to change the surface from blue tape to Kapton. I was too lazy to peel the blue off first, and thereby inadvertently discovered this hack.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;why-not-both.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Of course, since Kapton is the actual surface, this technique is suitable for any materials which already like to be printed on it. I print in TPU, PLA, and PETG, and have found that it works great for them all.&lt;/p&gt;
&lt;p&gt;The Kap tape does transfer a bit of the texture from the layer underneath, however you still get the overall shiny surface finish on the bottom that Kap alone normally provides.&lt;/p&gt;
&lt;p&gt;It is fairly easy to remove the Kapton, then the blue tape can be reused with a new Kap surface put over it again. I prefer to apply the Kap tape in the opposite direction to the blue, in order to make it easier to remove without taking the blue with it.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>A 60% ortho keyboard with a trackpad: tamago v2</title>
      <link>https://lo.calho.st/posts/tamago-v2-prototype/</link>
      <pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/tamago-v2-prototype/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been about five months since I released the &lt;a href=&#34;https://lo.calho.st/posts/tamago60/&#34; &gt;tamago60&lt;/a&gt;.
In this dramatically delayed follow-up, I&amp;rsquo;ve made a few improvements. It&amp;rsquo;s not 100% done yet,
but here&amp;rsquo;s a preview.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago-v2-assembly.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve got an aluminum switchplate, a 40mm Cirque trackpad, and 58 hotswap
sockets. All on a board that fits in a standard 60% tray-mount case.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a KB2040 module stuck in the middle of it, which conveniently
breaks out USB D+/D-, thus allowing me to relocate the USB port to the
normal location on a 60%.&lt;/p&gt;
&lt;p&gt;The trackpad is secured firmly to the switchplate by a 3D-printed
mount.&lt;/p&gt;
&lt;p&gt;I decided to finally try a PCBA service, and it went pretty seamlessly,
except for the part where I put the Choc socket part number on the BOM
instead of the MX one, thus delaying production by an extra week.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago-v2-built.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are a few changes I&amp;rsquo;d like to make before the full release of this one.&lt;/p&gt;
&lt;p&gt;The plan is to add some physical buttons for the trackpad, stick an RP2040
onboard, and maybe add an OLED. I&amp;rsquo;d like to switch to Choc sockets as well,
but I think I recall that they would have interfered with the mounting features.&lt;/p&gt;
&lt;p&gt;The design needs a bit of cosmetic polish before it looks like a premium
keyboard. However, the typing feel is great. The sound is pretty good too,
even without any lube or foam. The stabilizers rattle a little, but that
can be easily fixed.&lt;/p&gt;
&lt;p&gt;Even in its current form, it is a very usable keyboard. At least the trackpad is
more practical than the last version&amp;rsquo;s joystick.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Streaming QR code detection in Home Assistant</title>
      <link>https://lo.calho.st/posts/homeassistant-qr-code-detection/</link>
      <pubDate>Wed, 27 Sep 2023 01:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/homeassistant-qr-code-detection/</guid>
      <description>&lt;p&gt;A while back, I &lt;a href=&#34;https://lo.calho.st/posts/homeassistant-keycards/&#34; &gt;set up keyfob access&lt;/a&gt;
using Home Assistant and ESPHome. It required bodging an RFID reader
onto my doorbell, and after some time its 3D printed enclosure has
yellowed. Today, out of the blue, an idea: ditch the keyfobs and scanner
for QR codes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Ignore the configuration suggested in this post if you&amp;rsquo;re trying to
install this add-on in Home Assistant itself. This is about an earlier prototype.
Follow the readme on GitHub instead.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Scroll to the bottom if you just want the GitHub link.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The plan was simple. First, eat the doorbell&amp;rsquo;s RTSP stream. Feed it into
OpenCV or something and detect QR codes. Then, throw the codes at the
Home Assistant API.&lt;/p&gt;
&lt;p&gt;It ended up being easier than I thought.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-qr-code.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the code for the initial proof of concept. Yep, that&amp;rsquo;s all of it. It took
about 15 minutes to write and test.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; re
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; datetime &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime, timedelta
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; cv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; homeassistant_api &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; ha
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; yaml &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; CLoader &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; Loader, CDumper &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; Dumper
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ImportError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; yaml &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Loader, Dumper
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DEBOUNCE_PERIOD &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; timedelta(seconds&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TAG_ID_PATTERN &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;compile(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://www.home-assistant.io/tag/([0-9a-f-]+)&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(config):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; open(config, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; fh:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        config &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; yaml&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;load(fh, Loader&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;Loader)[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ha-cam-tag&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stream &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;VideoCapture(config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;camera&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;stream&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    detector &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;QRCodeDetector()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    last_time, last_tag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; ha&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Client(config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;homeassistant&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;uri&amp;#39;&lt;/span&gt;], config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;homeassistant&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;auth-token&amp;#39;&lt;/span&gt;]) &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; client:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; stream&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;isOpened():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _, frame &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; stream&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; data &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; detector&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;detectAndDecode(frame)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; m &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; TAG_ID_PATTERN&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;match&lt;/span&gt;(data):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    cur_time, tag_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(), m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; last_tag &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; tag_id &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; last_time &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; cur_time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; DEBOUNCE_PERIOD:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fire_event(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tag_scanned&amp;#34;&lt;/span&gt;, tag_id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;tag_id, device_id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;config[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;camera&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;device-id&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        last_time, last_tag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cur_time, tag_id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stream&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;release()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;destroyAllWindows()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sys&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exit(main(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;sys&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;argv[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:]))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The final version has the detector running in a separate thread (this helps with latency),
and some boilerplate to make it easier to run, but the core concept is the same.&lt;/p&gt;
&lt;p&gt;This requires just three packages: pyyaml, opencv-python, and homeassistant_api.&lt;/p&gt;
&lt;p&gt;It eats a config file that looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;ha-cam-tag&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;homeassistant&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;uri&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;https://homeassistant.example.com/api/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;auth-token&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;secret-token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;camera&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;device-id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;stream&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;rtsp://10.1.20.xxx:7447/xxxxxxxxxxxxxxxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m using the high resolution RTSP stream on my Unifi doorbell. The medium level worked too,
but was a bit less reliable.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got this running in a separate container from Home Assistant on my Proxmox box for now, but
I want to port it to a proper extension eventually.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the repo: &lt;a href=&#34;https://github.com/tmick0/ha-cam-tag&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/ha-cam-tag&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>It&#39;s always DNS; or: why I closed my Comcast Business account</title>
      <link>https://lo.calho.st/posts/comcast-dns-interception/</link>
      <pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/comcast-dns-interception/</guid>
      <description>&lt;p&gt;Warning: this post consists of a lot of grumbling and not much technical merit.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t usually make blog posts just to complain. However today is different.
I&amp;rsquo;m getting a new roof installed and this means I had to
temporarily disconnect the coax that brings internet service into my house, due to the way the cable is routed across it.
I was hoping this would be minimally disruptive, because I pay Comcast Business
$210/mo for a level of service that includes a backup LTE modem.&lt;/p&gt;
&lt;p&gt;As you can assume from the fact that I&amp;rsquo;m writing this post, it was disruptive anyway.&lt;/p&gt;
&lt;p&gt;Comcast&amp;rsquo;s &amp;ldquo;Connection Pro&amp;rdquo; LTE modem sits on the AT&amp;amp;T network and in the past
has behaved pretty well. However, for the past day it has been doing something
weird. I cannot get to many websites, all failing with &lt;code&gt;PR_END_OF_FILE_ERROR&lt;/code&gt;
in Firefox.&lt;/p&gt;
&lt;p&gt;So far, I have found that behavior on the following sites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;amazon.com&lt;/li&gt;
&lt;li&gt;bestbuy.com&lt;/li&gt;
&lt;li&gt;ebay.com&lt;/li&gt;
&lt;li&gt;mozilla.org&lt;/li&gt;
&lt;li&gt;newegg.com&lt;/li&gt;
&lt;li&gt;store.ui.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Upon further investigation, I discovered that these all resolve to the same IP.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;;; ANSWER SECTION:
aws.amazon.com.         0       IN      A       192.73.252.25
aws.amazon.com.         0       IN      A       192.73.252.18
;; ANSWER SECTION:
bestbuy.com.            0       IN      A       192.73.252.18
bestbuy.com.            0       IN      A       192.73.252.25
;; ANSWER SECTION:
ebay.com.               0       IN      A       192.73.252.25
ebay.com.               0       IN      A       192.73.252.18
;; ANSWER SECTION:
mozilla.org.            0       IN      A       192.73.252.25
mozilla.org.            0       IN      A       192.73.252.18
;; ANSWER SECTION:
newegg.com.             0       IN      A       192.73.252.18
newegg.com.             0       IN      A       192.73.252.25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And no, this should not be the case.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t just the behavior of some default resolver that was gifted to me in DHCP -
I have hardcoded an upstream.&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t matter what upstream resolver I use, the DNS requests are intercepted
and I get the same bogus reply:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.16.1-Ubuntu &amp;lt;&amp;lt;&amp;gt;&amp;gt; store.ui.com @8.8.8.8
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 7391
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;store.ui.com.                  IN      A

;; ANSWER SECTION:
store.ui.com.           0       IN      A       192.73.252.25
store.ui.com.           0       IN      A       192.73.252.18

;; Query time: 116 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Sep 27 11:00:40 MDT 2023
;; MSG SIZE  rcvd: 73
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.16.1-Ubuntu &amp;lt;&amp;lt;&amp;gt;&amp;gt; store.ui.com @1.1.1.1
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 1691
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;store.ui.com.                  IN      A

;; ANSWER SECTION:
store.ui.com.           0       IN      A       192.73.252.18
store.ui.com.           0       IN      A       192.73.252.25

;; Query time: 69 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Wed Sep 27 11:00:43 MDT 2023
;; MSG SIZE  rcvd: 73
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s find out who they belong to.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;25.252.73.192.in-addr.arpa. 14400 IN    PTR     pxy02-nsjc-c2szps.001.prd.c2szps.spscld.net.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Domain Name: SPSCLD.NET
Registry Domain ID: 2551237048_DOMAIN_NET-VRSN
Registrar WHOIS Server: whois.markmonitor.com
Registrar URL: http://www.markmonitor.com
Updated Date: 2022-04-11T16:44:25Z
Creation Date: 2020-08-06T17:43:40Z
Registry Expiry Date: 2024-08-06T17:43:40Z
Registrar: MarkMonitor Inc.
Registrar IANA ID: 292
Registrar Abuse Contact Email: abusecomplaints@markmonitor.com
Registrar Abuse Contact Phone: +1.2086851750
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MarkMonitor appears to offer some kind of network security service. Further digging reveals
that they provide the product that Comcast calls &amp;ldquo;SecurityEdge.&amp;rdquo; I explicitly turned this off on my
DOCSIS modem on day one. I cannot configure the LTE modem myself, so have to contact support to have it
disabled.&lt;/p&gt;
&lt;p&gt;Frankly, I find it &lt;em&gt;unacceptable&lt;/em&gt; that my ISP is attempting to intercept traffic to these sites,
and has enabled this feature without my consent. It would be different if it were blocking known malware or abuse
sites, but there is no reason for intercepting these. The fact that this behavior changed on its own
is alarming.&lt;/p&gt;
&lt;p&gt;I have lost all trust in Comcast and have given the required 30 day notice to close my account.
I have to pay a $770 penalty to break my contract. I will be finding another ISP.&lt;/p&gt;
&lt;p&gt;It is not just this sketchy behavior making me do this, either. Before I diagnosed the issue myself, both chat and phone support techs said there was no way to help me. Only after this investigation was I able to call again and tell them exactly how to fix it.&lt;/p&gt;
&lt;p&gt;I also recall an interaction I had with support about a year ago, when my internet was going out daily for hours at a time. Upon complaining about this, they stated:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We don&amp;rsquo;t cause outages, we fix them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Right.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Geocircles</title>
      <link>https://lo.calho.st/posts/geocircles/</link>
      <pubDate>Thu, 24 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/geocircles/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s time to write a post about Geocircles. According to whois, I registered
the domain over a year ago, in July 2022. The first commit was made even
earlier, in February of that year.&lt;/p&gt;
&lt;h2 id=&#34;history&#34;&gt;History&lt;/h2&gt;
&lt;p&gt;If you haven&amp;rsquo;t seen it before, &lt;a href=&#34;https://geocircles.io&#34; target=&#34;_blank&#34; &gt;Geocircles&lt;/a&gt; is a game
where you try to place a Street View location on the map. It was originally
born as a two-player, turn based game. One player would pick a location; the other
would guess. Then, they&amp;rsquo;d switch.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a frontend guy, so the UI was pretty clunky at first.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;old-screenshot.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;I originally stood up a proof-of-concept for this on my VPS, written in Python with
the Flask framework. It couldn&amp;rsquo;t really support more than one game at a time. While
I was working on scaling up the infrastructure, I released the daily-challenge version
that is currently playable.&lt;/p&gt;
&lt;p&gt;Over the course of a year, I collected 365 locations for the daily challenge. It recently
ran out and started to repeat.&lt;/p&gt;
&lt;p&gt;The UI has improved a little bit since then. Still not perfect, though.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;geocircles.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;So what happened to the two-player version? Well, I did rewrite the whole backend in Go.
I designed a scalable architecture and deployed the thing in AWS. And, well, nobody
played it. It only cost me about $10 a month to run, but when nobody actually played
a game after two months I shut it down. That happened about a year ago now.&lt;/p&gt;
&lt;p&gt;The daily challenge, at least, has a few loyal participants.&lt;/p&gt;
&lt;p&gt;This post isn&amp;rsquo;t for me to complain about the lack of interest, though. It&amp;rsquo;s to look at
the architecture.&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The backend of Geocircles was split into two parts: an API service and a session service. The API service handled creation of games, and the session service hosted the games themselves.&lt;/p&gt;
&lt;p&gt;The API service was entirely stateless. Instead of storing anything in a database, it just signed tokens that enabled players to connect to their sessions. This is basically all that it was responsible for.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;arch.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The session service needed state, but this state was only needed for the duration of a particular game and thus could be stored in memory.&lt;/p&gt;
&lt;p&gt;Both services were deployed as fleets of ECS containers. In front of them, a router service and a load balancer. Another service served the static frontend assets. Nothing too interesting to talk about there.&lt;/p&gt;
&lt;h2 id=&#34;events&#34;&gt;Events&lt;/h2&gt;
&lt;p&gt;Nearly every interaction with the frontend resulted in some state being transmitted to the game&amp;rsquo;s assigned session worker. These events were then relayed to the other player in the session.&lt;/p&gt;
&lt;p&gt;For example, if one player moves their panorama view, it generates a &lt;code&gt;pano_update&lt;/code&gt; event with the new pitch, heading, and zoom level. Then, the other player would receive it so they could watch them explore. Submitting a challenge or a guess also generated an event. A guess would also have a flag added by the backend, to mark it as correct or incorrect.&lt;/p&gt;
&lt;p&gt;Based on the events it received, the session would transition between four states: player one choosing a challenge, player two solving the challenge, player two choosing, then player one solving. This would then repeat.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;states.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;In the initial state, one player just waits for the other to connect. The state names here hint that the player who created the session was internally designated as the &amp;ldquo;host,&amp;rdquo; and the other as the &amp;ldquo;guest.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;routing&#34;&gt;Routing&lt;/h2&gt;
&lt;p&gt;Websockets handled this event transmission and state synchronization, in order to stay real-time. This presented a challenge. I didn&amp;rsquo;t want the backend to need to do any kind of synchronization, so it was desirable to somehow route both players in any particular session to the same session worker.&lt;/p&gt;
&lt;p&gt;To achieve this without storing any shared state at all, I implemented a custom router service. It frequently polled ECS for an up-to-date set of session workers. This wasn&amp;rsquo;t perfect, but worked well enough. Then, requests were routed using consistent hashing, on the session ID. Once I figured that out, the rest was easy. Golang provides some pretty nice utilities for implementing a reverse proxy.&lt;/p&gt;
&lt;p&gt;However, it meant I had to structure URLs such that the session ID was accessible to the router.&lt;/p&gt;
&lt;h2 id=&#34;apis-and-tokens&#34;&gt;APIs and tokens&lt;/h2&gt;
&lt;p&gt;All told, the API only really has a few unique endpoints, looking like this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/api/v1&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/new-game&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/join-game&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/session&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&amp;lt;game-id&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&amp;lt;user-id&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;new-game&lt;/code&gt; endpoint would generate a &lt;code&gt;game-id&lt;/code&gt;, and  a &lt;code&gt;user-id&lt;/code&gt; for each player. Each would call &lt;code&gt;join-game&lt;/code&gt; to generate an auth token before connecting to the session. The auth token essentially consists of &lt;code&gt;game-id&lt;/code&gt;, &lt;code&gt;user-id&lt;/code&gt;, and a timestamp &amp;ndash; all wrapped in an AES-GCM envelope. The tokens are then passed over the websocket in order to authenticate to the session worker.&lt;/p&gt;
&lt;p&gt;This extra step was added because the auth token itself is too long to include in a nicely shareable URL. In retrospect, it isn&amp;rsquo;t necessary because the &lt;code&gt;user-id&lt;/code&gt; is essentially treated like a credential anyway in this design. I think I had some plans to eventually prevent reuse, but it ended up not being worthwhile. It&amp;rsquo;s not like there&amp;rsquo;s any serious consequence here anyway.&lt;/p&gt;
&lt;h2 id=&#34;future&#34;&gt;Future&lt;/h2&gt;
&lt;p&gt;I might still re-launch multiplayer someday. I have some ideas to reduce the infrastructure cost to near-zero. In fact, its original launch was quite over-engineered. Everything is deployed by CloudFormation and supports autoscaling.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t load tested it, but when I do I expect to find that a single process can serve hundreds, if not thousands, of simultaneous sessions. At least I know that it&amp;rsquo;ll scale when I need it to.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Moving to Hugo and Cloudflare Pages</title>
      <link>https://lo.calho.st/posts/hugo/</link>
      <pubDate>Wed, 23 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/hugo/</guid>
      <description>&lt;p&gt;tl;dr: I migrated my blog from Jekyll to Hugo. It seems more better. I did my
best to fix everything, but you find something broken, do let me know.&lt;/p&gt;
&lt;p&gt;Previously, I had set my blog up on Github Pages with Jekyll as the SSG. I found
Jekyll increasingly difficult to work with since then, so decided it was time to
modernize by switching to Hugo. The directory structure makes a lot more sense
with Hugo, and builds are noticeably faster. Getting it set up on a new box
is also way easier, since you don&amp;rsquo;t have to fight with all the Ruby dependency
nonsense. It&amp;rsquo;s pretty much a self-contained package.&lt;/p&gt;
&lt;p&gt;GitHub pages was also somewhat annoying to get set up, but at least once it was
configured it was set-and-forget. However, I took this opportunity to migrate
anyway. I had set up some other blogs on Cloudflare Pages and found it very
easy to work with. Builds of my site are faster there than they were with
GitHub, but that might just be because of Hugo anyway. Since I already manage
my DNS with Cloudflare, it only took a few clicks to get set up with a custom
domain and all.&lt;/p&gt;
&lt;p&gt;I did have to make a few changes to content to get everything working nicely.&lt;/p&gt;
&lt;p&gt;I moved all my posts from standalone markdown files to directories, so I could
co-locate the related images with them. First, this meant I had to change all of
the image references from absolute to relative URLs &amp;ndash; that was simple enough.&lt;/p&gt;
&lt;p&gt;Next, I had to do something to avoid breaking existing links to posts. With Jekyll,
my &lt;code&gt;yyyy-mm-dd-slug.md&lt;/code&gt; filenames for posts would result in urls like &lt;code&gt;/posts/slug&lt;/code&gt;,
so I added the corresponding &lt;code&gt;slug&lt;/code&gt; to the front matter of each post with this awful
one-liner:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in ./*; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; slug&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;echo $f | sed -re &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s/^.\/[0-9-]+//&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;; sed -ire &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/date: /e echo slug: &lt;/span&gt;$slug&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; $f/index.md; &lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The final major fix was to get all the inline math rendering nicely with KaTex. Previously,
I used one macro for all math, and had it all rendering as inline. That macro now made it
all render as block, so I had to configure a custom one for inline and update all the posts
that needed to use it.&lt;/p&gt;
&lt;p&gt;Of course, theme customization was also necessary, and is largely still in progress.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>The homelab post</title>
      <link>https://lo.calho.st/posts/homelab/</link>
      <pubDate>Wed, 23 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/homelab/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s finally time for a post about my homelab. Yes,
it&amp;rsquo;s overly complicated. No, it&amp;rsquo;s not the most extensive.&lt;/p&gt;
&lt;p&gt;I had planned this post months ago (maybe a year ago) and never
got around to writing it. I even made a diagram.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;homelab.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;We have five (5!) VLANs on our home network.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One for random IoT crap, restricted from talking to
the rest of the LAN.&lt;/li&gt;
&lt;li&gt;One for the surveilance system.&lt;/li&gt;
&lt;li&gt;One for Home Assistant, its tablet UIs, various ESPHome devices.&lt;/li&gt;
&lt;li&gt;One for my work laptop.&lt;/li&gt;
&lt;li&gt;One for a Steam Link to talk to a gaming VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these, except for the Steam Link one, also has a corresponding
SSID. Then, there is one more SSID for the default VLAN.&lt;/p&gt;
&lt;p&gt;The majority of the infrastructural hardware is from Ubiquiti. It
plays nice together and is easy enough to configure.
The only exceptions are our two modems, provided by
Comcast.&lt;/p&gt;
&lt;h2 id=&#34;the-core-box&#34;&gt;The &amp;ldquo;core&amp;rdquo; box&lt;/h2&gt;
&lt;p&gt;The coaxial drop for the internet comes down into a centrally located
closet. There, one can currently find a 9u wall-mounted cabinet
containing a 16-port PoE switch, a USG serving as a router, an
LTE modem, a DOCSIS modem, some PoE injectors, a CloudKey, and a PDU
&amp;ndash; all hooked up to a little 500VA UPS.&lt;/p&gt;
&lt;p&gt;The LTE modem serves as a backup when the primary cable internet
goes down. Both modems are leased from Comcast Business, because
for some reason they do not allow you to provide your own as they
do for residential service.&lt;/p&gt;
&lt;p&gt;There is a dedicated 20 amp circuit dropped at this box, as in a
previous incarnation this closet was also home to a few beefier
servers. This is the fourth different enclosure that this closet
has held in three years. I do not keep it tidy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;core-box.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;From here, ethernet branches out to the roof, my office, the living
room, and the primary bedroom.&lt;/p&gt;
&lt;h2 id=&#34;the-office&#34;&gt;The office&lt;/h2&gt;
&lt;p&gt;In my office, we find another enclosure &amp;ndash; this one only 6u. It
contains a 24-port switch, a UNVR (surveillance video recorder),
and a Synology NAS, all attached to a 1500VA UPS.&lt;/p&gt;
&lt;p&gt;This enclosure was once wall-mounted, but since has had casters
jury-rigged to it and now lives on the floor. This switch used to
sit in the core, but now most of its ports remain unutilized.&lt;/p&gt;
&lt;p&gt;Atop this cabinet, we find an HP Z820 workstation with Proxmox
installed. Inside are two E5-2670v2&amp;rsquo;s, 8TB worth of Intel DC SSDs,
and an RTX 3060.&lt;/p&gt;
&lt;p&gt;In addition to running some services like Home Assistant and a
DNS server, there is also a Windows VM on this box to which
the video card is attached. This box has its own dedicated
1500VA UPS, identical to the one in the cabinet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;hp-server-cat.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;My cat likes to sit on top of it. The dongles hanging haphazardly
off the workstation are for Zigbee and Bluetooth.&lt;/p&gt;
&lt;p&gt;Also located in my office is a little 8-port POE switch.
Attached to it is an AP, a printer, and everything on
my desk.&lt;/p&gt;
&lt;h2 id=&#34;the-roof&#34;&gt;The roof&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a little three-port switch, powered by passive PoE, on the
roof. It provides passthrough power to three Unifi cameras.&lt;/p&gt;
&lt;p&gt;Another one of the ethernet drops on the roof crosses over to go into
the garage. A third port connects an outdoor AP.&lt;/p&gt;
&lt;h2 id=&#34;the-living-room&#34;&gt;The living room&lt;/h2&gt;
&lt;p&gt;A UAP-IW-HD connects a Roku and the Steam Link, and also
provides wifi coverage for the rest of the house. A very
convenient piece of hardware.&lt;/p&gt;
&lt;h2 id=&#34;home-assistant-stuff&#34;&gt;Home Assistant stuff&lt;/h2&gt;
&lt;p&gt;The main Home Assistant VM runs on that HP box. The
ZigBee dongle attached to it lets it communicate with 40
other devices such as buttons, sensors, lightbulbs, relays,
and switches.&lt;/p&gt;
&lt;p&gt;An enclosure formerly home to an alarm system mainboard now hosts a
Rasperry Pi, which monitors the old system&amp;rsquo;s sensors and
transmits their state via MQTT.&lt;/p&gt;
&lt;p&gt;I have three Raspberry Pis with touchscreens serving as
interfaces to Home Assistant. Two are wired in, powered by
PoE splitters. A third was not in a convenient place for
a new ethernet drop, so that one&amp;rsquo;s on wifi.&lt;/p&gt;
&lt;p&gt;Other fun things on the Home Assistant VLAN include an
ESPHome-based access reader, air quality sensors, smart
speakers, a garage door open, power monitors for the
washer and dryer, and an irrigation controller.&lt;/p&gt;
&lt;h2 id=&#34;others&#34;&gt;Others&lt;/h2&gt;
&lt;p&gt;In order to avoid TLS warnings on every internal service,
I&amp;rsquo;ve set up Traefik VM to front them all with a custom domain.
It gets a certificate from Let&amp;rsquo;s Encryupt using a DNS-based
challenge, as otherwise the host is not accessible from WAN.&lt;/p&gt;
&lt;p&gt;DNS is provided by Pi-hole, filtering requests for undesirable
domains.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>I designed another weird keyboard: tamago60</title>
      <link>https://lo.calho.st/posts/tamago60/</link>
      <pubDate>Mon, 24 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/tamago60/</guid>
      <description>&lt;p&gt;The original egg58 prototype used Choc V2 switches, which have MX-style
stems and therefore support a wide selection of keycaps. However, it turned
out that DSA, the shortest keycap profile widely available, would still bottom
out if the switches were plate-mounted. The egg58 layout was not really conducive
to PCB mounting, so I switched to original Choc switches.&lt;/p&gt;
&lt;p&gt;This left me with a supply of Choc V2 switches and DSA caps which have gone unused
for over a year now. I thought the egg58 was my endgame. It probably still is, but
nonetheless I have made another keyboard. Just to put those spare parts to use.&lt;/p&gt;
&lt;p&gt;I decided early on to go with an ortholinear layout, approximately 60% but arranged
similarly to the egg58. At some point, I started considering adding a trackpoint
to the board, like what they have on ThinkPads. It turned out that the availability
of trackpoint modules isn&amp;rsquo;t too great, and there&amp;rsquo;s difficult to integrate, so I settled
for a small joystick instead.&lt;/p&gt;
&lt;p&gt;I stuck the joystick right in the middle of the keyboard, added two additional keys
for left and right click, and landed on a semi-split design.&lt;/p&gt;
&lt;p&gt;After a few hours in KiCad, I ended up with this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago60-kicad.jpg&#34; alt=&#34;pcb layout&#34;&gt;&lt;/p&gt;
&lt;p&gt;The only difficult part was figuring out a solution for the 2u positions. As you
can see here, there are three switch positions for the 2u keys. I found that some
of my 2u keycaps had the stem in the middle, while others had two stems placed 1u
apart. Therefore, I needed the provide the option to populate switches to support
either.&lt;/p&gt;
&lt;p&gt;I wanted to target using a Pro Micro for the MCU, since I had a bunch of spares
of those lying around too. It turns out that it has just enough pins to support
a 10x6 matrix and two ADC channels for the joystick. There is no wasted I/O on
this board.&lt;/p&gt;
&lt;p&gt;The next step was to order the prototypes. &lt;a href=&#34;https://www.pcbway.com&#34; target=&#34;_blank&#34; &gt;PCBWay&lt;/a&gt; kindly
reached out to sponsor PCB prototyping for this project. Their ordering process was
straightforward, with a lot of options available.  Notably, they offer a matte black
solder mask, which was not available from my previous PCB supplier. Having seen it on
other peoples&amp;rsquo; projects, I knew it would look great for this keyboard.&lt;/p&gt;
&lt;p&gt;Unfortunately, right after submitting my order, I noticed some errors in the design.
I missed copying some of the mounting holes when altering the footprint for those weird
2u positions. Fortunately, once I fixed it, their support team was able to quickly replace
the Gerber files before the boards went to production.&lt;/p&gt;
&lt;p&gt;It only took a week for the boards to arrive. The matte black finish looks great,
and there is a noticible improvement in the quality of details compared to the PCB
producer I used previously. The cuts are smoother than I&amp;rsquo;m used to, and the silkscreen
resolution seems higher.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago60-bare.jpg&#34; alt=&#34;bare PCBs&#34;&gt;&lt;/p&gt;
&lt;p&gt;I also got an SMT stencil from PCBWay, which made it incredibly easy to assemble the board.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago60-assembled.jpg&#34; alt=&#34;assembled PCBs&#34;&gt;&lt;/p&gt;
&lt;p&gt;After adding switches, keycaps, and a 3D printed case, the tamago60 is complete.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tamago60.jpg&#34; alt=&#34;final assembled board&#34;&gt;&lt;/p&gt;
&lt;p&gt;Of course, the design is &lt;a href=&#34;https://github.com/eggsworks/tamago60&#34; target=&#34;_blank&#34; &gt;open source&lt;/a&gt;.
I&amp;rsquo;ve also shared the project on PCBWay so you can easily order your own board:&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>I made my custom keyboard wireless</title>
      <link>https://lo.calho.st/posts/wireless-keyboard/</link>
      <pubDate>Mon, 20 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/wireless-keyboard/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t have a particular need for a wireless keyboard. I&amp;rsquo;m usually
planted at my desk, with my laptop plugged into a dock and a
wired keyboard readily available.&lt;/p&gt;
&lt;p&gt;But, I thought it would be cool to make a Bluetooth version of the egg58.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;egg58bt.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It turns out, making a keyboard you&amp;rsquo;ve already designed wireless
is pretty straightforward these days. Using a &lt;a href=&#34;https://nicekeyboards.com/nice-nano/&#34; target=&#34;_blank&#34; &gt;nice!nano&lt;/a&gt;
microcontroller and &lt;a href=&#34;https://zmk.dev/&#34; target=&#34;_blank&#34; &gt;ZMK&lt;/a&gt;, you can turn basically any keyboard
based around the Pro Micro wireless almost instantly.&lt;/p&gt;
&lt;p&gt;The nice!nano is a microcontroller module pin-compatible with
the ubiquitous Pro Micro, but based around the nRF52840.
That means it comes with built-in wireless connectivity. &lt;em&gt;(Obligatory disclaimer: not sponsored, I just think it&amp;rsquo;s really neat.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;ZMK, as you could guess, is a keyboard firmware, but unlike QMK
has support for BLE (and nRF chips). Of course, it even works for split keyboards.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&#34;https://zmk.dev/power-profiler&#34; target=&#34;_blank&#34; &gt;ZMK&amp;rsquo;s power profiler&lt;/a&gt;,
I can expect the left (main) side of my keyboard to last almost two
weeks on a  charge, with just a tiny 110mAh battery that fits
conveniently under the nice!nano. The right half lasts much longer,
allegedly only needing a charge every 3 months&lt;/p&gt;
&lt;p&gt;Even though I didn&amp;rsquo;t need to, I altered my PCB design a bit,
just to remove the positions for components that were no longer
required. Of course, RGB had to go, since it would kill the battery
immediately.&lt;/p&gt;
&lt;p&gt;Anyway, the design files are &lt;a href=&#34;https://github.com/tmick0/egg58&#34; target=&#34;_blank&#34; &gt;here&lt;/a&gt; and firmware stuff &lt;a href=&#34;https://github.com/tmick0/zmk-config&#34; target=&#34;_blank&#34; &gt;here&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>The intransmissible packet</title>
      <link>https://lo.calho.st/posts/intransmissible/</link>
      <pubDate>Mon, 06 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/intransmissible/</guid>
      <description>&lt;p&gt;In any network, it is a given that there will be loss. We&amp;rsquo;ve been making computers talk to each other for decades now, and that whole time scientists and engineers have been searching for better ways to compensate for weird things the network does to data in flight.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;packet_loss.png&#34; alt=&#34;&#34;&gt;
&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;DALL-E, prompted &amp;ldquo;a lost packet in a computer network. minimal illustration, contemporary style, no text.&amp;quot;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been nearly 50 years since TCP was bestowed upon us, and for the most part it&amp;rsquo;s still the best we can do. Ignoring witchcraft like erasure codes, reliability is usually achieved through detection and retransmission. That is, if the sender thinks the receiver didn&amp;rsquo;t get a packet, it will just resend it. So, even when you roll your own reliability on top of UDP, it still usually ends up looking pretty similar to TCP. Even with all the improvements we&amp;rsquo;ve made, it&amp;rsquo;s still TCP.&lt;/p&gt;
&lt;p&gt;Historically, this has worked pretty well. Network loss tends to be either sporadic or persistent. Once you&amp;rsquo;ve figured out collision and congestion, if you experience loss and a few retries don&amp;rsquo;t help then you&amp;rsquo;re probably out of luck.&lt;/p&gt;
&lt;p&gt;But, what if you can&amp;rsquo;t retransmit that packet? What if &lt;em&gt;that specific packet&lt;/em&gt; just can&amp;rsquo;t be delivered? The next packet arrives just fine. But retransmission isn&amp;rsquo;t helping the first one.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;I was working on a project that involved processing high-bandwidth streaming data. Neither the data nor the processing matter, because today we were just playing some recorded data back to test network throughput. We had just upgraded the infrastructure, so we wanted to see what it could do.&lt;/p&gt;
&lt;p&gt;A file was stored on one machine, and some software to eat it was running somewhere else. Because we wanted to avoid head-of-line blocking in this application, we had a custom reliability protocol over UDP. We could tolerate a loss if it meant avoiding a latency spike.&lt;/p&gt;
&lt;p&gt;We had a huge library of test data, and had been playing most of it back without problems. Occasional loss was normal and expected. However, there was one dataset that we found would &lt;em&gt;always&lt;/em&gt; drop the first packet. Normally, we wouldn&amp;rsquo;t even notice this. But, this dataset had a lower packet rate so the delayed start was perceptible.&lt;/p&gt;
&lt;p&gt;The first few times we observed this, we thought nothing of it. The network did something weird. But, it was repeatable. And the reliability protocol had more than enough time to compensate for a random loss event. Why didn&amp;rsquo;t it in this case?&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;We were convinced we had discovered a bug. We started turning knobs to figure out under which conditions it could be reproduced. The first thing we tried tuning was the packet size. Originally, we were sending packets with a payload of something like 32 KiB. What happens if it&amp;rsquo;s only 8 KiB? No bug. 64 KiB? Still bugged.&lt;/p&gt;
&lt;p&gt;I figured there must be something wrong with how we&amp;rsquo;re handling large buffers in either the sender or receiver. I started staring at the code. I analyzed the unit tests for a gap. But both programs were simple enough and tested comprehensively enough that I was at a loss.&lt;/p&gt;
&lt;p&gt;We eventually took the new code and ran it on the old infrastructure. It worked there. This is truly an enigma.&lt;/p&gt;
&lt;p&gt;Something inspired another engineer to start twiddling bits. Somehow, he figured out that changing either of two adjacent bytes at a certain offset in the file could bypass the bug. I knew this had been a crucial discovery.&lt;/p&gt;
&lt;p&gt;I gave up on analyzing the code and convinced someone with more permissions than myself to collect a packet capture from both the source and destination hosts. I opened it in Wireshark and the first thing I saw was an IP packet that failed to be reassembled.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Sometimes, you start a packet capture at a bad time and miss something. That wasn&amp;rsquo;t the case here. On the sender side, I could see four fragments in the first datagram, sent about five seconds into the recording. On the receiver, I could see the first, third, and fourth fragments. The second fragment was nowhere to be seen.&lt;/p&gt;
&lt;p&gt;Yes, the 32 KiB payload required fragmentation even with jumbo frames - I am not sure why that was the default size, and didn&amp;rsquo;t care to find out. But, I had to figure out why fragmentation was breaking the transmission of this file.&lt;/p&gt;
&lt;p&gt;I continued looking at the packet captures - I could see several attempts at retransmission of this problem datagram, all unsuccessful. The second fragment never made it to its destination.&lt;/p&gt;
&lt;p&gt;Going back to the other engineer&amp;rsquo;s discovery, I tried to find those two bytes he had been able to manipulate to evade the bug. Looking back to the sender capture, I found they landed on the third and fourth bytes of the IP payload in the frame that was getting dropped.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0x0e 0xc8
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As a 16-bit big endian integer, that&amp;rsquo;s 3784. And it landed right where a UDP port number should go.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Port 3784 belongs to a protocol called &lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc5881&#34; target=&#34;_blank&#34; &gt;Bidirectional Forwarding Detection&lt;/a&gt;, or BFD. In short, it allows network devices to send each other high-rate pings in order to detect when a link fails. That should be irrelevant, because our packets were not destined for port 3784. And yet, under fragmentation, that port number ended up in the spot that it would if it were.&lt;/p&gt;
&lt;p&gt;Thus, we finally had a hunch. Something on the network is eating this packet, because it erroneously thinks it&amp;rsquo;s a BFD message.&lt;/p&gt;
&lt;p&gt;We tested some other scenarios. What if the destination port were indeed 3784? The failure is reproduced every time, even without fragmentation. If we put those two bytes at the same offset in the third fragment instead? The same. We took this as confirmation of our hypothesis.&lt;/p&gt;
&lt;p&gt;Indeed, even without a valid UDP header, this IP fragment was being interpreted as BFD and taken up into the control plane, just on the basis of &amp;ldquo;yep, that&amp;rsquo;s where the port number should go.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The new infrastructure&amp;rsquo;s switches supported BFD, while the old ones didn&amp;rsquo;t. We weren&amp;rsquo;t using the feature, and in fact it was turned off, but the problem occurred nonetheless.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;We filed a support case with Cisco, who let us know that they were already aware of the bug. It was related to another issue reported to them by another customer. This was a bit anticlimactic. But, fortunately that meant it was only a couple of weeks before a firmware update was available to fix this.&lt;/p&gt;
&lt;p&gt;Along with the firmware update, we were given a link to &lt;a href=&#34;https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-nxos-bfd-dos-wGQXrzxn&#34; target=&#34;_blank&#34; &gt;a security advisory&lt;/a&gt; regarding it. Apparently there was actually a DOS vulnerability hiding somewhere behind this bug, now known as &lt;a href=&#34;https://nvd.nist.gov/vuln/detail/CVE-2022-20623&#34; target=&#34;_blank&#34; &gt;CVE-2022-20623&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;I am not a security researcher, so I wasn&amp;rsquo;t concerned with finding a vulnerability but rather just fixing a bug. I wasn&amp;rsquo;t disappointed to not be first to discover it - I was just happy to have had an interesting problem to solve. Nonetheless, sometimes when I recount this story to friends I may take a bit more credit than I deserve, hinting toward Cisco&amp;rsquo;s attribution,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This vulnerability was found during the resolution of a Cisco TAC support case.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and taking it as a small claim to fame.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Keycard door access in Home Assistant</title>
      <link>https://lo.calho.st/posts/homeassistant-keycards/</link>
      <pubDate>Thu, 12 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/homeassistant-keycards/</guid>
      <description>&lt;p&gt;I first started fantasizing about keycard access to my home when Ubiquiti announced
the G4 Doorbell Pro. I&amp;rsquo;ve had the original G4 Doorbell for a few years now, and it&amp;rsquo;s been
great. However, when the G4 Doorbell Pro finally came out of Early Access, it lacked the
RFID feature that was originally advertised.&lt;/p&gt;
&lt;p&gt;Based on community consensus, it doesn&amp;rsquo;t seem like we&amp;rsquo;ll be getting that feature anytime
soon, so I decided to build my own RFID reader that would semi-integrate with the G4 Doorbell I
already had.&lt;/p&gt;
&lt;p&gt;Using ESPHome, an inexpensive NFC module, and a 3D printed enclosure, I sort of got what I wanted.&lt;/p&gt;
&lt;p&gt;Home Assistant has &lt;a href=&#34;https://www.home-assistant.io/integrations/tag/&#34; target=&#34;_blank&#34; &gt;built-in support for RFID tags&lt;/a&gt;,
which I could leverage to trigger an automation. All I really needed to do was figure out how to feed
it tags, and find a lock that would integrate.&lt;/p&gt;
&lt;p&gt;I ended up using a &lt;a href=&#34;https://www.amazon.com/dp/B09HYYZH6R&#34; target=&#34;_blank&#34; &gt;Kwikset Convert&lt;/a&gt; to add Zigbee control
to the back of my Schlage deadbolt, since it was less ugly than Schlage&amp;rsquo;s own options and I didn&amp;rsquo;t
want to rekey everything again.&lt;/p&gt;
&lt;p&gt;I borrowed the BOM from &lt;a href=&#34;https://github.com/adonno/tagreader&#34; target=&#34;_blank&#34; &gt;an existing Home Assistant tag reader project&lt;/a&gt;
and added some components to power it. I changed the WS2812 for an SK9822, since I already had some on
the shelf. I also removed the buzzer since I wouldn&amp;rsquo;t really want to use it anyway.&lt;/p&gt;
&lt;p&gt;My original plan was to make this device wireless, but the ESP8266 makes deep-sleep hard to work with,
so the 820 mAh battery I originally speced only lasted 6 hours. Thus, I resorted to hardwiring.&lt;/p&gt;
&lt;p&gt;Using the 1N4001 as a half-wave rectifier with &lt;a href=&#34;https://www.amazon.com/dp/B076H3XHXP/&#34; target=&#34;_blank&#34; &gt;some buck converters I already had&lt;/a&gt;,
I was able to get a usable 5V DC from the doorbell&amp;rsquo;s 18V AC circuit. The converter&amp;rsquo;s built-in 100uF capacitor
was sufficient to smooth the rectified voltage.&lt;/p&gt;
&lt;p&gt;I forgot to take a picture of the hardwired version, so here&amp;rsquo;s the guts from the battery-powered iteration:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell_rfid_internals.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;After assembly, I had to figure out the correct incantations to get everything working in ESPHome. I modified the
yaml from the above project, added some other stuff from the internet, and ended up with this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;esphome&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;door_card_reader&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;on_boot&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;priority&lt;/span&gt;: -&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;wait_until&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;api.connected&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;light.turn_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;brightness&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;flash_length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;2000ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;esp8266&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;board&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;esp01_1m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;framework&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;2.7.4&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# required for fastled on this platform&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;logger&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;api&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;encryption&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;(removed)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;ota&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;password&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;(removed)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;wifi&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;ssid&lt;/span&gt;: !&lt;span style=&#34;color:#ae81ff&#34;&gt;secret wifi_ssid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;password&lt;/span&gt;: !&lt;span style=&#34;color:#ae81ff&#34;&gt;secret wifi_password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;captive_portal&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;i2c&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;scan&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;frequency&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;400kHz&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;time&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;sntp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;on_time&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;seconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;if&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;condition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;api.connected&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;light.turn_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;brightness&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;flash_length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;250ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;light.turn_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;brightness&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;flash_length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;250ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;pn532_i2c&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pn532_board&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;on_tag&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;if&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;condition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;api.connected&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;light.turn_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;brightness&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;flash_length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;500ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;delay&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;homeassistant.tag_scanned&lt;/span&gt;: !&lt;span style=&#34;color:#ae81ff&#34;&gt;lambda |-&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ae81ff&#34;&gt;return x;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;light.turn_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;brightness&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;flash_length&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;500ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;delay&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;on_tag_removed&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;then&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;homeassistant.event&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;event&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;esphome.tag_removed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;light&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;fastled_spi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;status_led&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;internal&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;chipset&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;APA102&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;clock_pin&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;data_pin&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;num_leds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;rgb_order&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;BGR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Door Card Reader Status LED&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;sensor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;wifi_signal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Door Card Reader Wifi Signal dB&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;wifi_signal_db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;update_interval&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;entity_category&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;diagnostic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;copy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;source_id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;wifi_signal_db&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Door Card Reader Wifi Signal Percent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;filters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;lambda&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;return min(max(2 * (x + 100.0), 0.0), 100.0);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;unit_of_measurement&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Signal %&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;entity_category&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;diagnostic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;ll do nothing special with tags, and just report them directly to Home Assistant.
It also has a nice status LED functionality.&lt;/p&gt;
&lt;p&gt;To avoid drilling a second hole in my door trim, I designed a bracket which would mount
behind the Unifi Doorbell and give me some space to splice the wires. The enclosure for
the card reader would essentially just be a box that I&amp;rsquo;d shove everything into, because I
was too lazy to design any mounting features.&lt;/p&gt;
&lt;p&gt;The model is available on &lt;a href=&#34;https://www.thingiverse.com/thing:5784297&#34; target=&#34;_blank&#34; &gt;Thingiverse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is what it looks like installed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell_rfid_mounted.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ignore the doorbell&amp;rsquo;s complaint about Wifi - it had just booted up and apparently hadn&amp;rsquo;t
found the AP yet.&lt;/p&gt;
&lt;p&gt;So far it is serving me well. It gives me the ability to hand out dirt-cheap tags
instead of get traditional keys copied every time I need someone to be able to get
into my house. This is great for guests and petsitters who you&amp;rsquo;d otherwise have to
trust to return the spare key before you need to give it to the next person.&lt;/p&gt;
&lt;p&gt;These tags also have the benefit of being easily revocable credentials. It&amp;rsquo;s a lot harder
to rekey all my locks than it is to remove the automation for a tag in Home Assistant. This
would mean a cloned credential also has less impact than a cloned key.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Typing on a weird keyboard: a retrospective</title>
      <link>https://lo.calho.st/posts/weird-keyboard-retrospective/</link>
      <pubDate>Thu, 29 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/weird-keyboard-retrospective/</guid>
      <description>&lt;p&gt;It has been just over a year since I finished assembling the first version
of the &lt;a href=&#34;https://lo.calho.st/posts/egg58&#34; &gt;egg58&lt;/a&gt;, my custom split keyboard.
Thus, it seems like an appropriate time to make note of how it has treated me.&lt;/p&gt;
&lt;p&gt;To recap, I once doubted the idea of a split or ortho keyboard. Eventually, I tried
out an Ergodox and came to realize it really does offer increased comfort. However,
eternally picky, I decided I needed to design my own layout. Thus, the egg58 was born.&lt;/p&gt;
&lt;p&gt;So, how has the keyboard performed for the past year? We&amp;rsquo;ll look at my typing speed,
how it has impacted my use of &amp;ldquo;normal&amp;rdquo; keyboards, and its ergonomics.&lt;/p&gt;
&lt;p&gt;The typing test results discussed here are from &lt;a href=&#34;https://www.keyhero.com/free-typing-test/&#34; target=&#34;_blank&#34; &gt;keyhero&lt;/a&gt;
and &lt;a href=&#34;https://www.speedcoder.net/&#34; target=&#34;_blank&#34; &gt;speedcoder&lt;/a&gt;. The egg58 tests were performed using Kailh
Choc Red Pro switches, and the standard layout tests were on a 60% ANSI with Kailh Box
Hako Violets, so it isn&amp;rsquo;t quite an apples-to-apples comparison.&lt;/p&gt;
&lt;h2 id=&#34;speed-and-progress&#34;&gt;Speed and progress&lt;/h2&gt;
&lt;p&gt;At first, I was discouraged as I could barely type on the thing. In my first day using
it, I averaged a spectacular 40 words per minute, down from my benchmark of 120 wpm
on my 60% ANSI. However, a &lt;a href=&#34;https://goos.blog/&#34; target=&#34;_blank&#34; &gt;wise waterfowl&lt;/a&gt; told me,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;split isn&amp;rsquo;t about speed&lt;br&gt;
it&amp;rsquo;s about not f***ing up your wrists&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and I preservered.&lt;/p&gt;
&lt;p&gt;Unfortunately, being able to type at a reasonable pace is ultimately part of my job description.
Therefore, over the next few months, I took a series of typing tests to gauge my progress.&lt;/p&gt;
&lt;p&gt;I took dozens on the first day, trying to see if I could build the muscle memory. I could not. My very
first test measured 24 wpm, and only twenty minutes later I reached 40 wpm. This is where I stayed for
the rest of the day.&lt;/p&gt;
&lt;p&gt;A week later, I was averaging 60 wpm. The next, 75 wpm. After a month, 100 wpm.&lt;/p&gt;
&lt;p&gt;Now, after a year, I am able to comfortably reach 120 words per minute.&lt;/p&gt;
&lt;p&gt;However, regular typing exams are not a complete measure of performance in this case. Being a software
engineer, I probably type a lot more symbols than the average bear. And, the egg58&amp;rsquo;s biggest deviation
from a standard ANSI layout is in where those keys are located. To that end, I scored 70 words per
minute in both C++ and Python tests.&lt;/p&gt;
&lt;h2 id=&#34;back-and-forth&#34;&gt;Back and forth&lt;/h2&gt;
&lt;p&gt;One of the complaints I heard about split keyboards is that it is difficult to switch between them
between them and a standard layout. For the first five months of the egg58&amp;rsquo;s tenure, I was forced
to use a normal keyboard at work. I suppose going back and forth every day allowed me to keep the
muscle memory during that time, as I did not really struggle to switch between them.&lt;/p&gt;
&lt;p&gt;However, in May I began a new remote job and began using the egg58 almost full time. The only exceptions
are gaming, where I was not able to adapt my muscle memory of where WASD are located, and the occasional use of
my laptop, which unfortunately has a staggered-row keyboard physically attached.&lt;/p&gt;
&lt;p&gt;Since then, my aptitude with a &amp;ldquo;normal&amp;rdquo; keyboard has somewhat faded &amp;ndash; I can only achieve 95 wpm on the
same keyboard where I generated my benchmark of 120 wpm one year ago. More notably, my accuracy is much
lower &amp;ndash; about 97% versus 99.9% with the split.&lt;/p&gt;
&lt;h2 id=&#34;comfort&#34;&gt;Comfort&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not an expert in ergonomics, but it certainly feels like using a split keyboard full time is more
comfortable. My wrists used to feel strained after a day of work, but no longer. I also had a weird
shoulder pain creep up every once in a while, but it too has ceased.&lt;/p&gt;
&lt;p&gt;The key point here seems to be that the angle of my wrists is not constrained by the layout of the
keyboard. I think that the staggered-column layout also helps. My fingers have to do less lateral movement
than with a staggered-row design.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The time I invested into the design of the egg58 was definitely worthwhile. In addition to being a valuable
learning experience in electronics design, it has given me a tangible benefit in not f***ing up my wrists.&lt;/p&gt;
&lt;p&gt;Also, it serves as a conversation piece whenever someone sees it in my office. But I&amp;rsquo;ve yet to have anyone
ask me to make them one.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Adapting CEC controls to RS-232</title>
      <link>https://lo.calho.st/posts/cec-to-rs-232/</link>
      <pubDate>Mon, 26 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/cec-to-rs-232/</guid>
      <description>&lt;p&gt;In this article, we will instill a Raspberry Pi with some magic in order for a TV to talk to a hifi amplifier.
By the end of the journey, we&amp;rsquo;ll have the Pi pretending to be an AV receiver, intercepting commands from the HDMI
CEC bus, and relaying them to the amp via a combination of serial and IR control outputs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Don&amp;rsquo;t feel like reading? &lt;a href=&#34;https://github.com/tmick0/cec2rs232&#34; target=&#34;_blank&#34; &gt;Here&amp;rsquo;s the GitHub link.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have always been a fan of the sound of an analog amplifier. I&amp;rsquo;m not the kind of audiophile
who is going to claim that I can discern the imperfections in audio reproduction from a standard
home theater receiver, but they certainly lack a warmth that analog equipment offers.&lt;/p&gt;
&lt;p&gt;Because of this, I&amp;rsquo;ve always used vintage audio gear. For the past five years or so,
my main integrated amplifier has been a Yamaha A-760. Unfortunately, it has been showing
its age, needing repairs of some sort every six months or so. I have finally
grown tired of opening it up and decided to modernize my setup.&lt;/p&gt;
&lt;p&gt;I found a modern hifi amp from Cambridge Audio, the CXA61. It has a TOSLINK DAC, A/B
stereo pairs, a builtin bluetooth receiver, and a design that meets my aesthetic preferences.
It was missing two things from my wishlist: CEC control and a phono preamp. The phono preamp
was an easy fix, just requiring adding a separate component to the setup. Achieving CEC control,
however, would be a small challenge.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that previously, I was using a standalone ARC DAC which attenuated the
line-level output based on CEC volume controls. The amplifier itself predated even CDs, so
full digital control was out of the question. Therefore, the feature was limited. Migrating
from ARC to TOSLINK caused me to lose even that, though at least now I could benefit from a much
better DAC.&lt;/p&gt;
&lt;p&gt;Anyway, the CXA61 has an RS-232 serial port. Unfortunately, it only exposes power, source and
mute controls over this interface. The IR remote control, however, is capable of controlling
volume. I wrote to Cambridge Audio about this discrepancy, and apparently it is due to there
being no feedback capability from the volume knob, so it might not behave properly in some
serial control systems. At least it has a 3.5mm TRS jack for IR repeater input, so the remote
control capabilities can be emulated relatively easily. I figured by hacking both the IR and
serial inputs, I should be able to create a full-featured CEC bridge.&lt;/p&gt;
&lt;p&gt;My requirements were as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the TV turns on, the amp should turn on and switch to the correct input.&lt;/li&gt;
&lt;li&gt;When the TV turns off, the amp should turn off.&lt;/li&gt;
&lt;li&gt;Volume and mute controls on the original TV remote work as intended.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The HDMI CEC standard defines a common protocol for devices to interoperate and handle
these inputs. CEC is a 1-wire serial bus that sits on its own pin in an HDMI port, and
seemed relatively easy to interface with. I was originally planning building some AVR-based
monstrosity to interact with the CEC bus, but then I realized I still had a spare Raspberry
Pi sitting on the bench. The Raspberry Pi&amp;rsquo;s HDMI port has built-in CEC support. There
would be no real hardware work required for this project.&lt;/p&gt;
&lt;p&gt;I hooked up the Pi to my TV, and used &lt;a href=&#34;https://manpages.debian.org/bullseye/cec-utils/cec-client.1.en.html&#34; target=&#34;_blank&#34; &gt;cec-client&lt;/a&gt;
to figure out what messages it emitted. Decoding the messages with &lt;a href=&#34;https://www.cec-o-matic.com/&#34; target=&#34;_blank&#34; &gt;CEC-o-Matic&lt;/a&gt;,
I learned that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If I present as an audio system, the TV will send a System Audio Mode Request when it turns on.&lt;/li&gt;
&lt;li&gt;The TV will broadcast a Standby message when it turns off.&lt;/li&gt;
&lt;li&gt;Volume controls and mute will be passed through as keypresses.&lt;/li&gt;
&lt;li&gt;Occasionally the TV will query for the audio system&amp;rsquo;s volume level and mute status.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found &lt;a href=&#34;https://github.com/konikvranik/pyCEC&#34; target=&#34;_blank&#34; &gt;pyCEC&lt;/a&gt; which would allow me to easily interface with
the CEC bus, handle these commands, and send the necessary replies. Now, it was simply a matter of
translating the controls to the CXA61&amp;rsquo;s native protocol.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&#34;https://github.com/pyserial/pyserial/&#34; target=&#34;_blank&#34; &gt;pyserial&lt;/a&gt; with an FTDI-based USB-to-RS-232 adapter
to handle the power and mute functions, and &lt;a href=&#34;https://pypi.org/project/PiIR/&#34; target=&#34;_blank&#34; &gt;PiIR&lt;/a&gt; to bitbang
IR codes over a GPIO pin which I spliced into a TRS cable for the IR port.&lt;/p&gt;
&lt;p&gt;The serial protocol was fortunately &lt;a href=&#34;https://techsupport.cambridgeaudio.com/hc/en-us/articles/360012981057-RS232-protocol-for-CXA61-CXA81&#34; target=&#34;_blank&#34; &gt;well documented&lt;/a&gt;, and PiIR could learn the IR codes off the original remote with
the help of an IR receiver module.&lt;/p&gt;
&lt;p&gt;After implementing these controls, I just had to tie everything together with the right CEC events.
Finally, I had created &lt;a href=&#34;https://github.com/tmick0/cec2rs232&#34; target=&#34;_blank&#34; &gt;cec2rs232&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In theory, this project should be flexible enough to work with other devices. For the time being,
I&amp;rsquo;ve only implemented the protocol of the one I own. Pull requests, however, are welcome.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Zip tie to smartify: the dumbest way to upgrade your appliance</title>
      <link>https://lo.calho.st/posts/homeassistant-dryer/</link>
      <pubDate>Fri, 16 Sep 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/homeassistant-dryer/</guid>
      <description>&lt;p&gt;Any owners of Samsung laundry appliances out there will know they play a distinctive
jingle when finishing a job. I find it incredibly annoying (mostly because it becomes
stuck in my head for at least two days after hearing it), however disabling
the chime causes me to forget to unload my clothes until I run out of boxers.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a fan of most smart home products, because I distrust the cloud and I like to have
control of my data. The only off-the-shelf &amp;ldquo;smart&amp;rdquo; products I have are simple Zigbee (802.15.4)
devices, and a Roomba (because it can be configured for local control, with its cloud
functionality disabled). Thus, when I bought a new washer and dryer a few years ago, I opted
for non-smart variants, thus sacrificing a built-in notification capability.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll exhibit my stubbornness by explaining how I used Home Assistant,
ESPHome, and some magic to get my dryer to tell me when it&amp;rsquo;s done without playing that
awful song.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.home-assistant.io/&#34; target=&#34;_blank&#34; &gt;Home Assistant&lt;/a&gt; is open source smart home management
software, and I&amp;rsquo;ve been using it for over a year now to control and monitor various things
in my home. The &lt;a href=&#34;https://esphome.io/&#34; target=&#34;_blank&#34; &gt;ESPHome&lt;/a&gt; is a related project that lets you easily
integrate &lt;a href=&#34;https://www.espressif.com/en/products/socs/esp32&#34; target=&#34;_blank&#34; &gt;Espressif&lt;/a&gt; microcontrollers
to monitor things that might not have native integrations.&lt;/p&gt;
&lt;p&gt;My first attempt to get monitoring for my washer and dryer in Home Assistant involved
&lt;a href=&#34;https://www.amazon.com/gp/product/B07PJT939B/&#34; target=&#34;_blank&#34; &gt;vibration sensors&lt;/a&gt;, however it turned out that
modern appliances are simply too well-balanced for this to be a reliable method of detecting if
they are running. Thus I switched my focus to determining whether they were actively using any
power.&lt;/p&gt;
&lt;p&gt;Due to pixie magic (i.e., electromagnetics, physics, etc.), it is possible to detect an electric
field when there is current flowing through a wire. Ideally, you can
&lt;a href=&#34;https://en.wikipedia.org/wiki/Current_clamp&#34; target=&#34;_blank&#34; &gt;clamp something around a wire&lt;/a&gt; and the job is done.
This is incredibly reliable, however requires separating the hot and neutral conductors, and I didn&amp;rsquo;t
feel like putting in that kind of effort.&lt;/p&gt;
&lt;p&gt;Instead, I redirected my effort into searching for a sensor that can detect current in a cord without
splitting it apart. I found &lt;a href=&#34;https://moderndevice.com/products/current-sensor&#34; target=&#34;_blank&#34; &gt;this module&lt;/a&gt; from
Modern Device, which uses a pair of Hall effect sensors to be able to differentially sense a pair of
conductors. Magic.&lt;/p&gt;
&lt;p&gt;I purchased a few &lt;a href=&#34;https://www.amazon.com/gp/product/B07R4MVSCY/&#34; target=&#34;_blank&#34; &gt;NodeMCU ESP8266&lt;/a&gt; to hook them up to.
In retrospect, ESP32 would have been a better choice, as it has multiple ADC lanes, and I could have used
one MCU for both appliances.&lt;/p&gt;
&lt;p&gt;I connected the sensor module to one, configured ESPHome, and flashed the firmware. The configuration
is incredibly straightforward.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;sensor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;adc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;pin&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;A0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Dryer current sensor voltage&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;update_interval&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;filters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;sliding_window_moving_average&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;window_size&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;send_every&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, the module is simply zip-tied to the dryer power cord.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;esp-dryer-thing.jpg&#34; alt=&#34;ESP8266 and current sensor attached to cord&#34;&gt;&lt;/p&gt;
&lt;p&gt;I did some laundry, and it got me a graph like this in Home Assistant:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;dryer-voltage-plot.png&#34; alt=&#34;Plot showing voltage measured from the current sensor while dryer is running&#34;&gt;&lt;/p&gt;
&lt;p&gt;I did not care to do any absolute calibration, so just set up an automation to notify when this voltage goes below
a threshold:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;alias&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Dryer notify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;description&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;trigger&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;platform&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;numeric_state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;entity_id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;sensor.dryer_current_sensor_voltage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;for&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;hours&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;minutes&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;seconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;below&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;condition&lt;/span&gt;: []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;action&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;service&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;notify.notify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;message&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Dryer has finished&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;mode&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;single&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. A lot of effort exerted, all for this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;dryer-notif.png&#34; alt=&#34;Notification that dryer finished&#34;&gt;&lt;/p&gt;
&lt;p&gt;The process for the washer was largely the same, but with a different voltage threshold.&lt;/p&gt;
&lt;p&gt;More Home Assistant content coming soon, maybe, if I&amp;rsquo;m motivated.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Scraping wildfire timeseries data with NLP</title>
      <link>https://lo.calho.st/posts/wildfire-data/</link>
      <pubDate>Sun, 05 Jun 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/wildfire-data/</guid>
      <description>&lt;p&gt;New Mexico is facing a severe fire season, and I wanted to visualize the progression
of the major fires that are currently ongoing. However, I could not find a usable source
of data on daily size and containment statistics.&lt;/p&gt;
&lt;p&gt;A repository of daily updates exists, however as articles rather than structured data.
I ended up scraping the website and applying natural language processing to extract
the desired data.&lt;/p&gt;
&lt;p&gt;The library and utility is available &lt;a href=&#34;https://github.com/tmick0/inciweb_timeseries_scraper&#34; target=&#34;_blank&#34; &gt;on my GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://inciweb.nwcg.gov/&#34; target=&#34;_blank&#34; &gt;InciWeb&lt;/a&gt; contains a collection of the daily updates for the
fires of interest, and by using &lt;a href=&#34;https://www.crummy.com/software/BeautifulSoup/bs4/doc/&#34; target=&#34;_blank&#34; &gt;Beautiful Soup&lt;/a&gt;
I was able to collect and scrape all the articles. This was fairly straightforward.&lt;/p&gt;
&lt;p&gt;Extracting the information of interest proved to be the more difficult task. Posts on
the site come from various agencies and thus do not follow a consistent format. In order
to handle all posts in generality, I turned to a NLP technique.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&#34;https://www.nltk.org/&#34; target=&#34;_blank&#34; &gt;NLTK&lt;/a&gt;, I converted the words of the articles into their stems,
then found the concordance lists of keywords pertaining to the data I needed.&lt;/p&gt;
&lt;p&gt;After tagging the concordance strings with parts of speech, I was able to find the closest
number to each keyword; this proved to be a fairly reliable heuristic.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_best_value&lt;/span&gt;(text, value_tag, label_values):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34; Apply the heuristic to extract the closest string with the specified tag
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        to one of the label values
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    concordance_list &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; l &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; label_values:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        concordance_list &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; text&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;concordance_list(l)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; concordance_list:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tags &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; nltk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pos_tag(nltk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokenize&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;word_tokenize(s&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;line))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        label_candidates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value_candidates &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i, (token, tag) &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(tags):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; tag &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; value_tag:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                value_candidates&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append((token, i))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;elif&lt;/span&gt; token&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lower() &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; label_values:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                label_candidates&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append((token, i))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; l, i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; label_candidates:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; v, j &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; value_candidates:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                d &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; abs(i &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; j)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; val &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; d &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; dist:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    dist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; d
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; val
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extract_data&lt;/span&gt;(raw_article_text):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34; Returns the size and containment of the fire by applying the heuristics to the text
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    stemmer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; nltk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stem&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;WordNetLemmatizer()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    words &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; nltk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tokenize&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;word_tokenize(raw_article_text)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tokens &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [stemmer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lemmatize(w) &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; w &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; words]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    text &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; nltk&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;text&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Text(tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_best_value(text, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;CD&amp;#39;&lt;/span&gt;, [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;acre&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;acres&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    containment &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_best_value(text, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;CD&amp;#34;&lt;/span&gt;, [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;contained&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;containment&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; size, containment
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The rest of the code is not interesting, but eventually I was able to automate
gathering most of the data I needed to produce this plot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;fires2022.png&#34; alt=&#34;Visualization of the progression of the fires&#34;&gt;&lt;/p&gt;
&lt;p&gt;I used the utility in the above GitHub repository to dump the timeseries data from
InciWeb to CSV, gathered some historical numbers from
&lt;a href=&#34;https://gacc.nifc.gov/swcc/predictive/intelligence/Historical/Fire_and_Resource_Data/Historical_Fires_Acres.htm&#34; target=&#34;_blank&#34; &gt;SWCC&lt;/a&gt;,
and wrote a script to put it all together with &lt;a href=&#34;https://matplotlib.org/&#34; target=&#34;_blank&#34; &gt;matplotlib&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Designing a split keyboard: the egg58</title>
      <link>https://lo.calho.st/posts/egg58/</link>
      <pubDate>Tue, 12 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/egg58/</guid>
      <description>&lt;p&gt;Late last year, I was finally exposed to the comfort of the Ergodox &amp;ndash; a staggered-column, split keyboard.
However, having been using 60% keyboards for the previous decade, I felt like it had too many keys. While
some minimalist split ortho options did exist, I found many of them too minimal or too diverged from the Ergodox
design, which I did like in theory.&lt;/p&gt;
&lt;p&gt;Like any sane person, I proceeded to learn an entirely new skill in order to fulfill my need for a keyboard
that was kinda like an Ergodox but with fewer keys.&lt;/p&gt;
&lt;h2 id=&#34;settling-on-a-layout&#34;&gt;Settling on a layout&lt;/h2&gt;
&lt;p&gt;There exists a tool for designing a custom keyboard layout: the appropriately named &lt;a href=&#34;https://www.keyboard-layout-editor.com/&#34; target=&#34;_blank&#34; &gt;keyboard-layout-editor.com&lt;/a&gt;.
I messed around with it for a while until I landed on a layout.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;keyboard-layout.png&#34; alt=&#34;layout&#34;&gt;&lt;/p&gt;
&lt;p&gt;The stagger mimics that from the ergodox, but the thumb cluster is reduced, the innermost column is removed, and the
left column is 1u.&lt;/p&gt;
&lt;p&gt;Because the tool doesn&amp;rsquo;t handle split keyboards well, I settled with having just designed half. I managed to generate a switchplate SVG from this design,
import it into a CAD tool, and 3D print a &amp;ldquo;test plate&amp;rdquo; of sorts so I could feel out the layout.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;layout-test.jpg&#34; alt=&#34;test plate&#34;&gt;&lt;/p&gt;
&lt;p&gt;It felt pretty comfortable, so I proceeded to figure out how to make this into something real. I decided I wanted the board to be low-profile but not
basically-a-laptop-profile, so I decided on Kailh Choc version 2 switches.&lt;/p&gt;
&lt;h2 id=&#34;a-crash-course-in-circuit-design&#34;&gt;A crash course in circuit design&lt;/h2&gt;
&lt;p&gt;The easy way out would have been to hand-wire a keyboard in a 3D printed case. However, I wanted some luxury features:
hotswap sockets and RGB.&lt;/p&gt;
&lt;p&gt;Neither of these would have been possible with a hand wired build, so I proceeded to install KiCad, an open source
electronic design automation suite. I have emphasized several times on this blog that I am not an electrical engineer.
Therefore, this new flavor of CAD was to be an entirely new skill.&lt;/p&gt;
&lt;p&gt;I at least had some vague idea of how schematics worked, so I managed to put something together. I won&amp;rsquo;t bore you with
a figure here, because it&amp;rsquo;s basically just &amp;ldquo;a bunch of switches connect to a microcontroller.&amp;rdquo; The PCB layout and trace
routing are a little bit more interesting, but I appear to have lost the original design files, so I can&amp;rsquo;t provide an
image of that for the first version either.&lt;/p&gt;
&lt;p&gt;After a couple weeks, I managed to complete my design and sent the Gerber files off to One Of Those Overseas Manufacturers
for prototype production.&lt;/p&gt;
&lt;p&gt;After about a month, some PCBs arrived. I built the keyboard. I forked QMK. I flashed firmware. It worked.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;egg58v1.jpg&#34; alt=&#34;version one&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ignore my dirty mousepad.&lt;/p&gt;
&lt;h2 id=&#34;getting-it-working-is-not-enough&#34;&gt;Getting it working is not enough&lt;/h2&gt;
&lt;p&gt;It turns out that the only keycap profile that really works with Choc v2 is DSA. There are also no
shine-through keycaps in DSA profile. And it&amp;rsquo;s hard to find a legended keycap set that will work well for
a custom layout like this.&lt;/p&gt;
&lt;p&gt;I eventually spent Too Much Money on a set of keycaps and still wasn&amp;rsquo;t satisfied, as with a switchplate
DSA still bottoms out. I then found a nice set of shine-through caps for Choc v1, and decided to redesign
the entire board around them.&lt;/p&gt;
&lt;p&gt;A new design cycle started, around the new switch footprint and targeted keycap set. A few other minor changes
were made. A logo was placed on the PCB, some prettification was done, and options for other MCUs and communication
protocols were added.&lt;/p&gt;
&lt;p&gt;Several months later, some PCBs showed up at my door. I built the board. I installed the keycaps.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;egg58v2.jpg&#34; alt=&#34;version two&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;epilogue&#34;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s been less than twenty four hours with the new keyboard. So far, I have nothing to complain about,
except the fact that this keycap set didnt have + and - keys.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;egg58.jpg&#34; alt=&#34;version two&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m happy with it for now.&lt;/p&gt;
&lt;p&gt;Here are some Github links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/tmick0/egg58&#34; target=&#34;_blank&#34; &gt;Design files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/tmick0/qmk_firmware/tree/egg58/keyboards/egg58&#34; target=&#34;_blank&#34; &gt;Firmware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    
    
    <item>
      <title>A demonstration of TDOA multilateration</title>
      <link>https://lo.calho.st/posts/tdoa-multilateration/</link>
      <pubDate>Sun, 20 Feb 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/tdoa-multilateration/</guid>
      <description>&lt;p&gt;Yesterday, a thread popped up on r/Albuquerque where users attempted to identify
the source of a loud explosion in the early morning. A few individuals provided their locations
and timestamps at which their surveillance cameras detected the sound. I thought I could use
this information to pinpoint its source using multilateration.&lt;/p&gt;
&lt;h2 id=&#34;what-is-multilateration&#34;&gt;What is multilateration?&lt;/h2&gt;
&lt;p&gt;Multilateration is the process by which an energy source can be located based on observations of the
energy at different locations. The energy can be a light, radio, acoustic, or seismic wave; the time
the wave takes to travel to each observer allows us to solve a system of equations to identify the unknown
source. The process works mostly the same for all types of waves, just the speed of transmission varies.
In this case, we&amp;rsquo;ll be using acoustic energy, which is fairly resilient to timing errors. Sound takes about
five seconds to travel one mile, so even if the timestamps are not accurate we can get fairly close to
the source.&lt;/p&gt;
&lt;p&gt;Multilateration has many real-world applications, for example the acoustic form we&amp;rsquo;re studying today
is used in &lt;a href=&#34;https://en.wikipedia.org/wiki/Gunfire_locator&#34; target=&#34;_blank&#34; &gt;law enforcement&lt;/a&gt; and
&lt;a href=&#34;https://en.wikipedia.org/wiki/Artillery_sound_ranging&#34; target=&#34;_blank&#34; &gt;defense&lt;/a&gt;, and its radio counterparts are used for
&lt;a href=&#34;https://en.wikipedia.org/wiki/ASDE-X&#34; target=&#34;_blank&#34; &gt;aircraft tracking&lt;/a&gt; and
&lt;a href=&#34;https://en.wikipedia.org/wiki/Global_Positioning_System#Geometric_interpretation&#34; target=&#34;_blank&#34; &gt;navigation&lt;/a&gt;.
Its wide range applicability has resulted in this being a very well-studied problem.
However, one might expect it to be difficult to understand. In this article, we&amp;rsquo;ll look
at the mathematics of multilateration and find in it a surprising simplicity.&lt;/p&gt;
&lt;h2 id=&#34;how-does-it-work&#34;&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;Since we want to locate the event based on the differences in times of detection, what we&amp;rsquo;re doing
is actually TDOA (time difference of arrival) multilateration. For simplicity, we&amp;rsquo;ll assume a flat
Earth and attempt to solve the problem in the Cartesian plane. If we know the difference in arrival
time between two points, we can describe the possible locations of the event as a hyperbola. In fact,
one of the ways a hyperbola can be defined is as the set of points which have a fixed distance between
two fixed points.&lt;/p&gt;
&lt;p&gt;The hyperbola we&amp;rsquo;re interested in can be described as&lt;/p&gt;
&lt;p&gt;$$\sqrt{(x - x_1)^2 + (y - y_1)^2} - \sqrt{(x - x_0)^2 + (y - y_0)^2} - v(t_1 - t_0) = 0$$&lt;/p&gt;
&lt;p&gt;where $x_0, y_0$ is the position of the first observer, $t_0$ is its observation time, $x_1, y_1, t_1$
are those parameters of the second observer, and $v$ is the propagation speed of the wave we&amp;rsquo;re interested in.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s easy to identify the two instances of the Cartesian distance formula here. In fact, the first square root
represents distance between the event and the second observer, and the second gives the distance between the
event and the first observer. Therefore, the equation can easily be parsed as providing the set of points
that have distance $v(t_1 - t_0)$ from both observers. We&amp;rsquo;ll refer to this quantity proportional to the
difference in arrival times as a pseudodistance.&lt;/p&gt;
&lt;p&gt;For example, consider the case where the two observers notice the event at the same time. We can intuit
that we should observe a line.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;hyperbola0.png&#34; alt=&#34;The hyperbola showing the points equidistant from two observers&#34;&gt;&lt;/p&gt;
&lt;p&gt;If we were to determine that the TDOA between the observers correlates to a pseudodistance of 3,
then we can begin to observe the development of the hyperbolic curvature. In this case, we&amp;rsquo;ve made
the event closer to the top-left observer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;hyperbola1.png&#34; alt=&#34;The hyperbola showing the points relating to an event 3 units closer to one observer&#34;&gt;&lt;/p&gt;
&lt;p&gt;By adding a third observer, we can draw a second hyperbola.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;hyperbola2.png&#34; alt=&#34;A second hyperbola drawn between the first and the new observer intersects with the first&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this case, we&amp;rsquo;ve set a pseudodistance of 2 between the first and third observers&amp;rsquo; observations. The intersection
between the two hyperbolae gives us a solution indicating the source of the event.&lt;/p&gt;
&lt;h2 id=&#34;a-practical-example&#34;&gt;A practical example&lt;/h2&gt;
&lt;p&gt;To protect the privacy of the users who contributed, we&amp;rsquo;ll walk through a fake example instead
of the one discussed on Reddit. I&amp;rsquo;ve generated some points in the middle of the desert with a synthetic
event. We&amp;rsquo;ll be using Python code to solve the multilateration.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s define the observation locations and timestamps.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;measurements &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#ae81ff&#34;&gt;34.888&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;103.826&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#ae81ff&#34;&gt;34.931&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;103.805&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.6&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [&lt;span style=&#34;color:#ae81ff&#34;&gt;34.921&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;103.781&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.5&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is simply a list of the latitudes and longitudes of the observers, with timestamps representing the
TDOA from the first observation in seconds.&lt;/p&gt;
&lt;p&gt;Now that we have input data, the first step is to convert the geodetic coordinates to something we can work
with more easily. To keep things simple, we&amp;rsquo;ll go to ENU (east-north-up) coordinates centered at the first
observer, and discard the up component to keep things in the plane.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pymap3d &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; pm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lat0, lon0, t0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; measurements[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(len(measurements)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lat, lon, t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; measurements[i]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    e, n, _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;geodetic2enu(lat, lon, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, lat0, lon0, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    measurements[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [e, n, t]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After this, &lt;code&gt;measurements&lt;/code&gt; has the content &lt;code&gt;[[0.0, 0.0, 0.0], [1918.6593908214895, 4770.574154674195, 0.6], [4111.911492926018, 3661.9046769939555, 2.5]]&lt;/code&gt;, where each entry gives us the distance east, north from the first observer (in meters) and its time
of observation.&lt;/p&gt;
&lt;p&gt;Since we&amp;rsquo;re working with meters and seconds, let&amp;rsquo;s define the speed of sound with respect to those units:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;speed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;343.&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# m/s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we need to figure out how to define and solve the system of hyperbolic equations. I&amp;rsquo;m going to use
&lt;a href=&#34;https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.leastsq.html&#34; target=&#34;_blank&#34; &gt;scipy&amp;rsquo;s least-squares solver&lt;/a&gt;
so we&amp;rsquo;ll specify the system in a way that we can use with it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; numpy &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;functions&lt;/span&gt;(x0, y0, x1, y1, x2, y2, d01, d02, d12):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34; Given observers at (x0, y0), (x1, y1), (x2, y2) and TDOA between observers d01, d02, d12, this closure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        returns a function that evaluates the system of three hyperbolae for given event x, y.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(args):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x, y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; d01
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; d02
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; d12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; [a, b, c]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; fn
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve added the third hyperbola which represents the TDOA between the second and third observers, in addition to the two
we have already discussed.&lt;/p&gt;
&lt;p&gt;To help out the solver a bit, we can also specify the Jacobian matrix of this system. This is just a collection of the
partial derivatives of each function with respect to each independent variable.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;jacobian&lt;/span&gt;(x0, y0, x1, y1, x2, y2, d01, d02, d12):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(args):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        x, y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        adx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bdx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cdx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ady &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        bdy &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        cdy &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            [adx, ady],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            [bdx, bdy],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            [cdx, cdy]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; fn
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a last preparation before solving, we can provide an initial guess for the location of the event.
We&amp;rsquo;ll just use the average of the three observer locations. This helps avoid issues with points for
which the function is not well-defined.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mean([x &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; x,y,t &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; measurements])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mean([y &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; x,y,t &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; measurements])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we can run the solver:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; scipy.optimize &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; opt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x0, y0, t0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; measurements[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x1, y1, t1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; measurements[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x2, y2, t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; measurements[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; functions(x0, y0, x1, y1, x2, y2, (t1 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t0) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed, (t2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t0) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed, (t2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t1) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;J &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; jacobian(x0, y0, x1, y1, x2, y2, (t1 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t0) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed, (t2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t0) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed, (t2 &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t1) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; speed)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x, y, _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; opt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;leastsq(F, x0&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[xp, yp], Dfun&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;J)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives us a solution of &lt;code&gt;1109.306162, 2213.711852&lt;/code&gt; in ENU space relative to the first receiver.
We can convert that back to geodetic coordinates to make it more useful:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lat, lon, _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;enu2geodetic(x, y, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, lat0, lon0, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We have now determined the location of the event to be &lt;code&gt;34.907954, -103.813862&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The system of hyperbolae and the solution can be visualized:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style=&#34;color:#66d9ef&#34;&gt;as&lt;/span&gt; plt
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Create reasonable x, y bounds for visualization&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max_x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(x0, x1, x2, x)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min_x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; min(x0, x1, x2, x)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;range_x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max_x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; min_x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min_x &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; range_x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max_x &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; range_x &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max_y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max(y0, y1, y2, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min_y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; min(y0, y1, y2, y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;range_y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; max_y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; min_y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;min_y &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; range_y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;max_y &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; range_y &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Create a grid of input coordinates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linspace(min_x, max_x, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ys &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linspace(min_y, max_y, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;xs, ys &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;meshgrid(xs, ys)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Evaluate the system across the grid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;A, B, C &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; F((xs, ys, np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;zeros(xs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;shape)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Plot the results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x0, y0, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x1, y1, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;g&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x2, y2, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x, y, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;k&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, A, [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;y&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, B, [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;m&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, C, [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve somewhat abused matplotlib&amp;rsquo;s &lt;code&gt;contour&lt;/code&gt; function here by evaluating the grid
and plotting the contour for just the zeros of the function. The result is the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tdoa-solved-system.png&#34; alt=&#34;Intersection of three hyperbolae solved for the event&#34;&gt;&lt;/p&gt;
&lt;p&gt;This allows us to visually confirm that the reported solution is at the intersection of
the hyperbolae.&lt;/p&gt;
&lt;p&gt;We can also derive each observer&amp;rsquo;s distance from the event and the time-of-flight of
the energy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;d0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y0, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;d1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y1, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;d2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; x2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; y2, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; d0 &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; speed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; d1 &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; speed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; d2 &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; speed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This lets us know that the distances from each observer are
2476 m, 2682 m, and 3334 m respectively; the corresponding times
of flight are 7.2 s, 7.8 s, and 9.7 s. This confirms the TDOAs
of 0.6 s and 2.5 s that we provided as input.&lt;/p&gt;
&lt;p&gt;We can also use this information to plot circles around each
observer, which gives us a more traditional triangulation
diagram, though in this case it is synthetic.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;circle&lt;/span&gt;(cx, cy, r):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(x, y):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(x &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; cx, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(y &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; cy, &lt;span style=&#34;color:#ae81ff&#34;&gt;2.&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; fn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; circle(x0, y0, d0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; circle(x1, y1, d1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; circle(x2, y2, d2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x0, y0, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x1, y1, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;g&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x2, y2, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;scatter(x, y, color&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;k&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, c0(xs, ys), [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, c1(xs, ys), [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;g&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;contour(xs, ys, c2(xs, ys), [&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], colors&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;b&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;tdoa-circles.png&#34; alt=&#34;TOF circles surrounding each observer&#34;&gt;&lt;/p&gt;
&lt;p&gt;We can see that the intersection of the circles also lies at the event.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I apologize that this is only a shallow overview of multilateration (mostly because I only have a
shallow understanding) and for the simplicity of the example. However, two-dimensional multilateration
still provides the groundwork for the three-dimensional applications I mentioned earlier. In those cases,
we&amp;rsquo;re solving for the intersection of hyperboloids instead of hyperbolae, for which at least four observers
are required. In applications like low-frequency radio waves, the curvature of the earth also needs to be
considered, making the mathematics much more complicated. In seismology, variations in Earth&amp;rsquo;s density also
introduce additional complexity. I like the acoustic example given here because of its simplicity, and
hopefully some readers found the exercise interesting nonetheless.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>How not to build a gaming PC</title>
      <link>https://lo.calho.st/posts/gaming-vm/</link>
      <pubDate>Sat, 05 Feb 2022 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/gaming-vm/</guid>
      <description>&lt;p&gt;Over the past month and a half, I&amp;rsquo;ve been battling the various ghosts and demons that have apparently
taken residence in the ancient server chassis that I am now using as a backend for a Steam Link. Since I doubt
anyone else on the Internet is insane enough to do this, I suppose I am mostly making this post for myself
to refer back to when it inevitably breaks. If, by chance, you do find this useful, then that is merely
a coincidence.&lt;/p&gt;
&lt;h2 id=&#34;prologue&#34;&gt;Prologue&lt;/h2&gt;
&lt;p&gt;My main PC runs Ubuntu, and I have been maining Linux for over a decade now. Most of the games I play
have native Linux support or at least work well in Proton. Sometimes, however, a game is more suited to
sitting on the couch with a controller than sitting at my desk with a keyboard and mouse. Even in those
cases, streaming to a Steam Link attached to my TV works well.&lt;/p&gt;
&lt;p&gt;Fall Guys was one such couch game, and unfortunately it also ended my run of smooth sailing with my
gaming experience on Linux. With the introduction of an anticheat in late 2020, it would no longer work
under Proton. To remedy this situation, I installed Windows on another SSD and I found myself dual booting
for the first time in at least eight years.&lt;/p&gt;
&lt;p&gt;Now, to play Fall Guys, I just had to get up off the couch, walk to my office, reboot into Windows, log in,
then connect to it from the Steam Link.&lt;/p&gt;
&lt;p&gt;This was clearly too much work, so it was obviously time to exert even more effort doing something stupid
to solve it.&lt;/p&gt;
&lt;h2 id=&#34;chapter-1-i-buy-some-hardware&#34;&gt;Chapter 1: I buy some hardware&lt;/h2&gt;
&lt;p&gt;Should the reader of the distant future not recall, it was impossible to buy a GPU from 2020 til at least 2022.
New cards were going out of stock instantly only to be slung on eBay for double the MSRP. Even old
cards, which might have gone for $100 used a few years ago, now cost hundreds more. Anyway, that&amp;rsquo;s how I
ended up with a Dell OEM Radeon RX 580, a card that is now approaching five years in age (I do not want to
discuss how much I paid for it).&lt;/p&gt;
&lt;p&gt;What will I be doing with this hunk of old silicon somehow worth its weight in gold, you ask? Why, shoving
it in an even older server, of course.&lt;/p&gt;
&lt;p&gt;I acquired an HP ProLiant DL380p Gen 8 chassis (for far less than I paid for the GPU, I might add)
and attempted to stick the card in it.&lt;/p&gt;
&lt;h2 id=&#34;chapter-2-this-is-the-part-of-the-story-where-i-start-to-dislike-hp&#34;&gt;Chapter 2: This is the part of the story where I start to dislike HP&lt;/h2&gt;
&lt;p&gt;The PCI risers that came with the chassis would not accomodate the card. Each (one per socket)
held a x16 and two x8 PCIe slots. The x16 was at the top, and thus my two-slot card could not physically
be installed.&lt;/p&gt;
&lt;p&gt;A few other varieties of risers were available for purchase. One has two x16 slots, and it turns out that
is the one that I needed to use. Upon acquiring it (after paying serveral times the value of the three-slot
riser I already had) I discovered that for some reason, it can only be installed on the second socket.
Yes, for some reason the second x16 slot of this riser would land on a slot that HP for some reason decided
would only be x8 on the first socket. Whatever, this isn&amp;rsquo;t the end of the world. It&amp;rsquo;s just slightly ridiculous
that my gaming VM will have to access resources on both NUMA nodes (since of course the network and SCSI adapters
reside on the first socket).&lt;/p&gt;
&lt;h2 id=&#34;chapter-3-did-i-mention-the-virtualization&#34;&gt;Chapter 3: Did I mention the virtualization?&lt;/h2&gt;
&lt;p&gt;Ah, yes &amp;ndash; virtualization. I installed Proxmox VE on the iron, after a relatively smooth
&lt;a href=&#34;https://openzfs.github.io/openzfs-docs/Getting%20Started/Debian/Debian%20Bullseye%20Root%20on%20ZFS.html&#34; target=&#34;_blank&#34; &gt;install with ZFS for root&lt;/a&gt;. It is only somewhat annoying that my &lt;code&gt;/boot&lt;/code&gt; partition
lies on a flash drive now. Did I mention this chassis doesn&amp;rsquo;t support UEFI?&lt;/p&gt;
&lt;p&gt;I configured the Windows VM with &lt;code&gt;ovmf&lt;/code&gt;, &lt;code&gt;pc-q35&lt;/code&gt;, &lt;code&gt;host&lt;/code&gt;, Virtio SCSI, and everything else the
Internet told me to do to get the best performance. I did the install and configured the basic
necessities, then it became time to attach the GPU.&lt;/p&gt;
&lt;p&gt;To passthrough the GPU to the VM, I jumped through several hoops, only some of which
were well-documented; some, I found, were unique to the experience of dealing with, well,
whatever HP did to this server.&lt;/p&gt;
&lt;p&gt;Proxmox provides &lt;a href=&#34;https://pve.proxmox.com/wiki/Pci_passthrough&#34; target=&#34;_blank&#34; &gt;pretty good documentation on setting up PCI passthrough&lt;/a&gt;,
so it was fairly easy to figure out that I needed to add &lt;code&gt;intel_iommu=on iommu=pt vfio-pci-ids=blah,blah&lt;/code&gt; to my
kernel commandline. I neglected to read the part about blacklisting the graphics drivers, so it took a few more reboots
(with very, very long POSTs) to remember to do that in order to get the &lt;code&gt;vfio&lt;/code&gt; module to actually handle the card.&lt;/p&gt;
&lt;p&gt;I attempted to attach the card to the VM, and it failed to start with &lt;code&gt;Operation not permitted&lt;/code&gt;. Several hours of
Googling led me to
&lt;a href=&#34;https://forum.level1techs.com/t/dl380p-proxmox-pci-passthrough-operation-not-permitted/159031/13#post_13&#34; target=&#34;_blank&#34; &gt;this post&lt;/a&gt;,
which is terrifyingly recent and makes me think I would not have been able to accomplish this feat if I had attempted it only
a year earlier, despite the age of the hardware.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://www.jimmdenton.com/proliant-intel-dpdk/&#34; target=&#34;_blank&#34; &gt;page he linked to&lt;/a&gt; explained some problem I didn&amp;rsquo;t understand, but
sure sounded like what was happening to me. Thus, I spent quite some time struggling to install these HP utilities
and after eventual success, started blindly typing commands to flip bits in the BIOS.&lt;/p&gt;
&lt;p&gt;Why would HP not make these things configurable in the regular BIOS ROM? Why would I have to acquire a separate
XML file that tells it which addresses to poke bits in? &lt;!-- raw HTML omitted --&gt;*shakes fist at HP*&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;These mysterious, and frankly frightening, incantations fortunately allowed the VM to finally boot.&lt;/p&gt;
&lt;h2 id=&#34;chapter-4-performance-anxiety&#34;&gt;Chapter 4: Performance anxiety&lt;/h2&gt;
&lt;p&gt;After getting the GPU drivers and everything set up, most of the games I was interested in playing
(themselves about as old as the server I was running them on) Just Worked &amp;ndash; but only half the time.
The other half, the VM would boot in a state where everything was unbearably slow.&lt;/p&gt;
&lt;p&gt;As previously alluded to, the GPU was bound to NUMA1, so the &amp;ldquo;half the time&amp;rdquo; it was slow is when the
VM landed on NUMA0. I Googled for some incantations to pin the VM to a node, and typed
&lt;code&gt;hookscript: local:snippets/pin-numa1.sh&lt;/code&gt; into &lt;code&gt;/etc/pve/qemu-server/${vm}.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The hookscript itself, living at &lt;code&gt;/var/lib/vz/snippets/pin-numa1.sh&lt;/code&gt; was the following kludge:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$2&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;post-start&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pid&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;&amp;lt; /run/qemu-server/&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;1&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;.pid&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cpus&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;8-15,24-31&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    taskset --cpu-list --all-tasks --pid $cpus $pid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I found this actually made the problem worse, because for some reason my system preferred
assigning the VM memory that belonged to to NUMA0 rather than NUMA1 now. Further Googling,
I obtained the following spell to put in the VM config:
&lt;code&gt;numa0: cpus=0-15,hostnodes=1,memory=12288,policy=bind&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, quite entertainingly, the VM would fail to start because for some reason, &lt;em&gt;other stuff&lt;/em&gt;
was now consuming too much of NUMA1&amp;rsquo;s memory to be able to make this reservation.&lt;/p&gt;
&lt;p&gt;Fortunately, this machine is old enough to be DDR3 and thus I was able to obtain enough RAM
(with DIMMS identical to those I was already running) to expand to 64 GB per socket for, well,
less than I paid for anything else that went into this box. This had the added benefit of using
all four of the channels available to each socket, instead of just one as was the case before.&lt;/p&gt;
&lt;p&gt;Finally, I was able to boot the VM and confirm with &lt;code&gt;numastat&lt;/code&gt; that the VM was running on
NUMA1, and its memory was local. Games were running smoothly&amp;hellip; most of them, at least.&lt;/p&gt;
&lt;h2 id=&#34;chapter-5-two-reasons-this-post-is-a-month-late&#34;&gt;Chapter 5: Two reasons this post is a month late&lt;/h2&gt;
&lt;p&gt;There was still something wrong.&lt;/p&gt;
&lt;p&gt;Dolphin emulator was running like utter garbage. GameCube games were almost passable, averaging about 25 FPS
with occasional lag spikes. Wii games were entirely unplayable. I looked at performance metrics &amp;ndash; nothing
would indicate that this was too much for the box to handle. I ran again to Google. I found
&lt;a href=&#34;https://forums.dolphin-emu.org/Thread-performance-issues-running-dolphin-under-kvm-hypervisor-no-really-this-should-work?pid=400958#pid400958&#34; target=&#34;_blank&#34; &gt;this post&lt;/a&gt;
which suggested it was a CPU scaling issue. I looked again at the numbers. Indeed, my CPUs were not boosting
above their most economical clock. But why? The performance governor was in place, but it did nothing.&lt;/p&gt;
&lt;p&gt;Hours of fruitless debugging later, I recalled something I saw in the BIOS. Yes, the actual BIOS, not the
feed-me-some-XML-so-I-can-twiddle-bits commandline utility&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;hp-bios.png&#34; alt=&#34;Power regulator config in the BIOS&#34;&gt;&lt;/p&gt;
&lt;p&gt;This option was set to &amp;ldquo;High Performance Mode,&amp;rdquo; which is code for &amp;ldquo;fast when HP&amp;rsquo;s heuristic thinks you&amp;rsquo;re
doing something hard.&amp;rdquo; Setting it to &amp;ldquo;OS Control Mode&amp;rdquo; allowed the governor to work as expected. The emulator
now worked (after switching it to DX11 mode &amp;ndash; for some reason OpenGL still ran poorly).&lt;/p&gt;
&lt;p&gt;One problem remained.&lt;/p&gt;
&lt;p&gt;Fall Guys, which inspired this whole adventure, would spontaneously crash. Not just crash, but cause the entire
VM to reset. Fortunately, the iron survived these events, and its &lt;code&gt;dmesg&lt;/code&gt; indicated that the graphics card
generated a NMI. Don&amp;rsquo;t worry, I don&amp;rsquo;t know what that means either, besides that it&amp;rsquo;s bad. Bad enough to also
show up in iLO logs with a big red icon next to it.&lt;/p&gt;
&lt;p&gt;I could have simply ignored this issue and played &lt;em&gt;all the other games&lt;/em&gt; in the meantime instead.
But no, I can&amp;rsquo;t give up now, I&amp;rsquo;ve done too much to quit so soon.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t even know what I typed into Google to find it, but
&lt;a href=&#34;https://patchwork.kernel.org/project/kvm/patch/1250686963-8357-38-git-send-email-avi@redhat.com/&#34; target=&#34;_blank&#34; &gt;this kernel patch&lt;/a&gt;
gave me a vague sense of hope.&lt;/p&gt;
&lt;p&gt;I typed &lt;code&gt;echo &#39;1&#39; &amp;gt; /sys/module/kvm/parameters/ignore_msrs&lt;/code&gt; into the iron and booted the VM back up. I launched
Fall Guys. I qualified from the first round. No crash. Surely, I will crash out in the next round&amp;hellip; Yet, I did not.
I played all the way through to the final, where I didn&amp;rsquo;t stand a chance for the crown.&lt;/p&gt;
&lt;p&gt;However, what I had obtained instead was far more valuable: validation. I added this parameter to GRUB.&lt;/p&gt;
&lt;h2 id=&#34;epilogue&#34;&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;About a year ago, I first had the thought that &amp;ldquo;it would be neat to have a gaming server for the Steam Link
to connect to.&amp;rdquo; Had I known how arduous this journey would be, I likely wouldn&amp;rsquo;t have embarked. Was it worth
it? Probably not. But maybe the real gaming server is the arcane knowledge we picked up along the way.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Revised smart doorbell adapter circuit</title>
      <link>https://lo.calho.st/posts/doorbell-adapter-revised/</link>
      <pubDate>Sat, 11 Dec 2021 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/doorbell-adapter-revised/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://lo.calho.st/posts/doorbell-adapter&#34; &gt;a previous post&lt;/a&gt;, I described issues encountered
when integrating the Unifi G4 Doorbell with a digital chimebox. I designed an adapter circuit, and it
worked for a few months; however, eventually I started encountering problems and upon further investigation
discovered that some of the assumptions I made about the behavior of the Unifi chime adapter module were
incorrect. With this new information, I designed a new circuit which is actually simpler and should be
more reliable.&lt;/p&gt;
&lt;p&gt;After about five months in operation, the circuit I detailed in the earlier post started emitting a
buzzing sound. The relay contacts were in fact vibrating, but why? The first stage of this investigation
involved hooking up the &amp;ldquo;output&amp;rdquo; leads from the module to my oscilloscope.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-scope-1.png&#34; alt=&#34;oscilloscope trace of voltage across the leads&#34;&gt;&lt;/p&gt;
&lt;p&gt;To the left of the trigger, we can see some bizarre modulated AC signal. This represents the &amp;ldquo;idle&amp;rdquo; state, where
the doorbell has not been pressed. The RMS voltage of this was too low for my cheap multimeter to register, and thus
I had incorrectly thought it was a zero volt output. While initially there were no consequences for this error,
after some time apparently the relay I chose decided this was enough voltage to cause it to oscillate.&lt;/p&gt;
&lt;p&gt;This discovery also made me realize that the characterization of these two leads as &amp;ldquo;outputs&amp;rdquo; was incorrect. I popped
open the chimebox module and discovered that there are really only two terminals inside; in fact, the two pairs of leads
sticking out of the box were mapped two these two terminals in parallel. That is, in the intended deployment scenario,
this module sits parallel to the chimebox.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;chime-module.jpg&#34; alt=&#34;internals of the Unifi chime adapter module&#34;&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t take time to fully reverse engineer the board, but identified a comparator and a relay. Nothing else in here can
effect any logic. Something strange is going on. Time for more fun with the scope.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-scope-2.png&#34; alt=&#34;oscilloscope trace, end of ring period&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here we can see the behavior when the ring period ends. There is a zero-volt period prior to the idle signal resuming.
My best guess at this point is that the adapter module clamps the voltage across the chime low (yielding that idle signal),
then when it detects a voltage increase effected by the doorbell itself, triggers the relay to stop clamping. The doorbell
later opens the circuit, resetting the module, then the idle resumes.&lt;/p&gt;
&lt;p&gt;I came up with a few ideas to filter out the idle stage (RC filter, comparator, attiny85) but landed on a fairly simple
design that I think should work reliably.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-adapter-revised-schematic.png&#34; alt=&#34;schematic of revised adapter circuit&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now armed with some knowledge of how the Unifi chime module works, the new adapter board can be designed with only three terminals: one
for each lead of the Unifi module, and one connecting to the chimebox. The two module leads are identified in the schematic as &amp;ldquo;AC,&amp;rdquo; connecting
back to the transformer, and &amp;ldquo;TRIGGER,&amp;rdquo; connecting to the doorbell.&lt;/p&gt;
&lt;p&gt;Using a half-bridge (single diode) rectifier (D1) and some smoothing (C1), we can get an estimate of the current voltage on the TRIGGER line,
and rely on a zener diode (D2) to determine when this passes the activated threshold. The zener is connected to the gate of a mosfet (Q1),
pulled low; the mosfet can then switch the relay (K1). When the relay is open, another half-bridge (D4) provides the idle supply to the chimebox.
A flyback diode (D3) has also been added across the coil of the relay.&lt;/p&gt;
&lt;p&gt;After some testing, this circuit seemed to work, so I put something together on protoboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;revised-adapter-module.jpg&#34; alt=&#34;adapter module on protoboard&#34;&gt;&lt;/p&gt;
&lt;p&gt;The first terminal connects to the transformer (AC), the second to the doorbell (TRIGGER), and the third to the chime. The other
terminal of the chime then connects back to the transformer (on the opposite side of the circuit).&lt;/p&gt;
&lt;p&gt;Note that the reverse face of the board has been flipped as to correspond directly to the front.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Pulseaudio configuration for soundboard routing</title>
      <link>https://lo.calho.st/posts/pulseaudio-soundboard/</link>
      <pubDate>Fri, 25 Jun 2021 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/pulseaudio-soundboard/</guid>
      <description>&lt;p&gt;Pulseaudio has been the default audio engine in Ubuntu for several years now, and
fortunately it has a very flexible module system that lets you route and mix audio.
Today, we&amp;rsquo;ll be looking at how we can set it up to mix mic and soundboard
inputs into a virtual source.&lt;/p&gt;
&lt;p&gt;I recently wrote a &lt;a href=&#34;https://github.com/tmick0/pymidisoundboard&#34; target=&#34;_blank&#34; &gt;simple soundboard app&lt;/a&gt;
in Python that uses a MIDI controller to trigger clips, and relied on the fact that
gstreamer would default to Pulseaudio output to later allow me to configure routing
and mixing without having to do anything specific in the code.&lt;/p&gt;
&lt;p&gt;The main system-wide Pulseaudio configuration lives in &lt;code&gt;/etc/pulse/default.pa&lt;/code&gt;; since
I&amp;rsquo;m the only user on my system I decided to just throw everything in there, but
&lt;a href=&#34;http://manpages.ubuntu.com/manpages/bionic/man5/default.pa.5.html&#34; target=&#34;_blank&#34; &gt;it is also possible to set this up per-user&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The relevant lines in my &lt;code&gt;default.pa&lt;/code&gt; are:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;load-module module-null-sink sink_name=soundboard_mix sink_properties=device.description=SoundboardMix
load-module module-combine-sink sink_name=soundboard_router slaves=alsa_output.pci-0000_00_1f.3.analog-stereo,soundboard_mix sink_properties=device.description=&amp;#34;SoundboardRouter&amp;#34;
load-module module-loopback sink=soundboard_mix source=mic_ec
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Respectively, these lines will&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a virtual output called &lt;code&gt;soundboard_mix&lt;/code&gt; where we will combine the soundboard and mic sources together; Pulseaudio will also automatically create a monitor called &lt;code&gt;soundboard_mix.monitor&lt;/code&gt; that we can use as a source.&lt;/li&gt;
&lt;li&gt;Create another virtual output called &lt;code&gt;soundboard_router&lt;/code&gt; that we can use to route the soundboard to both our main output (in my case, &lt;code&gt;alsa_output.pci-0000_00_1f.3.analog-stereo&lt;/code&gt;) as well as &lt;code&gt;soundboard_mix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Route our mic (in my case, &lt;code&gt;mic_ec&lt;/code&gt;, since I have an echo cancellation module configured) into the &lt;code&gt;soundboard_mix&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To find the names for the mic source and main output, use &lt;code&gt;pactl list sources&lt;/code&gt; and &lt;code&gt;pactl list sinks&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;Now, all we have to do is make sure the soundboard application and whatever app in which we want to use our audio source are passed environment variables to choose the correct source and sink. Pulseaudio respects
variables &lt;code&gt;PULSE_SOURCE&lt;/code&gt; and &lt;code&gt;PULSE_SINK&lt;/code&gt;, so in my case I can launch my soundboard app with &lt;code&gt;PULSE_SINK=soundboard_router&lt;/code&gt; and launch the app in which I want to use it with &lt;code&gt;PULSE_SOURCE=soundboard_mix.monitor&lt;/code&gt;
and everything will function as desired.&lt;/p&gt;
&lt;p&gt;As an added bonus, here&amp;rsquo;s the line I&amp;rsquo;m using to create my echo-cancelled mic source:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;load-module module-echo-cancel source_name=&amp;#34;mic_ec&amp;#34; use_master_format=1 aec_method=&amp;#34;webrtc&amp;#34; source_master=&amp;#34;alsa_input.pci-0000_00_1f.3.analog-stereo&amp;#34; sink_master=&amp;#34;alsa_output.pci-0000_00_1f.3.analog-stereo&amp;#34; aec_args=&amp;#34;analog_gain_control=0 digital_gain_control=0 mobile=1 routing_mode=quiet-earpiece-or-headset comfort_noise=0 intelligibility_enhancer=1 high_pass_filter=0&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Change &lt;code&gt;alsa_input.pci-0000_00_1f.3.analog-stereo&lt;/code&gt; to the correct input as listed by &lt;code&gt;pactl list sources&lt;/code&gt;. I have a lot of extra options supplied here, which
are &lt;a href=&#34;https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/#module-echo-cancel&#34; target=&#34;_blank&#34; &gt;documented nicely here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also, feel free to check out &lt;a href=&#34;https://github.com/tmick0/pymidisoundboard&#34; target=&#34;_blank&#34; &gt;my soundboard app&lt;/a&gt; if you&amp;rsquo;re interested in something very simple to trigger clips.
It features a pyqt5 GUI for configuration and automatic MIDI note detection for setting triggers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;soundboard.png&#34; alt=&#34;soundboard&#34;&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Adapting a smart doorbell to an incompatible digital chime</title>
      <link>https://lo.calho.st/posts/doorbell-adapter/</link>
      <pubDate>Sat, 19 Jun 2021 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/doorbell-adapter/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update (2021-12-11):&lt;/strong&gt; It turns out some of the assumptions I initially made were incorrect, and the adapter
circuit described within eventually stopped working. After further investigation, I have made a revised circuit.
Refer to &lt;a href=&#34;https://lo.calho.st/posts/doorbell-adapter-revised&#34; &gt;the new post&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For a few months, I&amp;rsquo;ve been using an &lt;a href=&#34;https://store.ui.com/products/uvc-g4-doorbell&#34; target=&#34;_blank&#34; &gt;Ubiquiti G4 Doorbell&lt;/a&gt;,
but recently chose to upgrade my mechanical chimebox to a digital one. Though The Ubiquiti store lists the
&lt;a href=&#34;https://www.broan-nutone.com/en-us/product/doorbells/la600wh&#34; target=&#34;_blank&#34; &gt;NuTone LA600WH&lt;/a&gt; as compatible, it turns out
there are some caveats which I managed to overcome by conjuring up an adapter from parts I had lying around.
This technique can likely be adapted to work with other doorbells or chimeboxes that don&amp;rsquo;t play nice with each
other, and avoids spending money on a commercially-available adapter.&lt;/p&gt;
&lt;p&gt;The main issue when using a smart doorbell with a digital chimebox seems to be that both want to
continuously draw power. A normal &amp;ldquo;dumb&amp;rdquo; doorbell will simply close a circuit to trigger a mechanical
chime. In the case of my digital chime, it required a diode in order to continuously receive power when
the button isn&amp;rsquo;t being pressed. However, my smart doorbell also requires a special hack to receive power &amp;ndash;
an adapter that Ubiquiti provides which sits between the doorbell and the chimebox, and triggers the chimebox
when the button is pressed. Unfortunately, these two hacks are inherently incompatible with one another.
Based on some searches, it seems that this is the case for a lot of doorbells and digital chimes.&lt;/p&gt;
&lt;p&gt;There were two &amp;ldquo;easy&amp;rdquo; ways to get the doorbell working, in the case of the LA600WH. Since it supports completely
wireless operation, there is an option to install three D-cell batteries, however who wants to change batteries
when a wired installation is possible? The second hack would have been to configure a long enough ring duration
in the Unifi Protect app for the chime to respond; in this case, there was unfortunately a long delay that seemed
to be due to the chime &amp;ldquo;booting up.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;It was at this point that I consulted Google: surely, someone has run into the same problem as me, right?
Well, yes. I managed to find &lt;a href=&#34;https://community.ui.com/questions/Successful-installation-of-G4-Doorbell-with-digital-chime/a02cba33-8899-4e53-8b26-dfdd71230bde&#34; target=&#34;_blank&#34; &gt;a how-to post in the Ubiquiti community&lt;/a&gt;
explaining how to get the G4 to play nice with a digital chime. Unfortunately, it required &lt;a href=&#34;https://bcsideas.com/products/vdbi-video-door-button-interface&#34; target=&#34;_blank&#34; &gt;something called the VDBI&lt;/a&gt;
to serve as an adapter. Not wanting to spend an additional $20 or wait for shipping, I turned to my parts shelf.&lt;/p&gt;
&lt;p&gt;After searching a few bins and finding a relay and some diodes, it occurred to me that I could easily build
something that would allow the G4 to pretend to be a regular, mechanical doorbell. Doorbell circuits use low-voltage AC
(in my case, 18V); the fact that my chimebox came with a diode led me to believe that it wants half-wave power in order
to stay powered on but idle. Then, full wave power should cause it to ring. The adapter provided with the G4 simply seemed
to provide power to the doorbell continuously, while also closing the circuit intended to ring the chime when the button is
pressed. If I could exploit the &amp;ldquo;output&amp;rdquo; of the Ubiquiti adapter to trigger a relay (or a triac, probably, but I didn&amp;rsquo;t have
one) that provides full-wave power, and use a diode to provide half-wave power continuously (as originally intended by the
manufacturer), the chimebox should work as it was meant to. Unfortunately, relays can typically only be triggered by DC (the
inductive properties of the internal coil don&amp;rsquo;t play nice with AC) but this could be remedied with a bridge rectifier. An
integrated one would work, but I didn&amp;rsquo;t have one, so I built one with discrete diodes. I considered omitting the relay and
just feeding whatever comes out of the Ubiquiti adapter directly to the chime, but not being sure what else is going on inside
there I didn&amp;rsquo;t want to backfeed it from the constant supply.&lt;/p&gt;
&lt;p&gt;The circuit I came up with looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-adapter-circuit.png&#34; alt=&#34;schematic&#34;&gt;&lt;/p&gt;
&lt;p&gt;The AC provided by the Ubiquiti adapter when the doorbell is pressed will be rectified in order to trigger the relay, causing
full-wave AC to be provided to the chime. When it isn&amp;rsquo;t being triggered, the 1N4002 diode that was provided with the chime will
provide it half-wave current, allowing it to stay powered-on but idle. As I mentioned, I ended up building the rectifier out
of discrete diodes; for what it&amp;rsquo;s worth, I used 1N4007s. It&amp;rsquo;s important that you use a relay that will tolerate the voltage coming
out of your doorbell transformer; in the case of 18VAC fully rectified, this would be about 16.2VDC. In some cases, you may also
need to add a smoothing capacitor; if the relay isn&amp;rsquo;t staying latched as long as you would hope, try adding one. You might also
be able to get away with a half-bridge rectifier, but I didn&amp;rsquo;t bother.&lt;/p&gt;
&lt;p&gt;I wasn&amp;rsquo;t originally intending to do this write-up, so unfortunately I didn&amp;rsquo;t take any pictures prior the installation,
but what I built ended up looking like this (after being wrapped in Kapton tape).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-adapter-build.jpg&#34; alt=&#34;adapter&#34;&gt;&lt;/p&gt;
&lt;p&gt;Yes, I know that&amp;rsquo;s a 5V relay, and I just told you not to do that. This is what I had around, but it hasn&amp;rsquo;t caught fire yet and
I&amp;rsquo;ll replace it eventually.&lt;/p&gt;
&lt;p&gt;Hooked into the Ubiquiti adapter and the chime, everything ends up looking like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-adapter-full-schematic.png&#34; alt=&#34;schematic&#34;&gt;&lt;/p&gt;
&lt;p&gt;It even all fits nicely into the battery compartment of the chime:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;doorbell-adapter-install.jpg&#34; alt=&#34;installation&#34;&gt;&lt;/p&gt;
&lt;p&gt;As mentioned in previous posts, I&amp;rsquo;m certainly not an electrical engineer, but fortunately I was able to summon enough
ability to solve this problem. Hopefully my solution can benefit others as well.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Dependency and build management for sourcemod with sourceknight</title>
      <link>https://lo.calho.st/posts/sourceknight/</link>
      <pubDate>Mon, 14 Jun 2021 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/sourceknight/</guid>
      <description>&lt;p&gt;Though I haven&amp;rsquo;t been playing with it for long, I&amp;rsquo;ve found that compiling plugins for sourcemod is a huge
hassle, mostly due to how dependencies are managed &amp;ndash; that is, they really aren&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;For those who don&amp;rsquo;t know what sourcemod is, you probably have no interest in reading this article. But,
if for whatever reason you want to read it anyway, &lt;a href=&#34;https://www.sourcemod.net/about.php&#34; target=&#34;_blank&#34; &gt;sourcemod is a mod for Source engine game servers&lt;/a&gt;
that provides support for custom scripts and plugins.&lt;/p&gt;
&lt;p&gt;The Source engine is fairly old, dating back to Half-Life 2, but many games based on it such as
Team Fortress 2 and Counter-Strike: Global Offensive still have a thriving community with active
custom servers. Many of these community servers rely on sourcemod to provide various features as well as
support for custom game modes.&lt;/p&gt;
&lt;p&gt;Unfortunately, the state of sourcemod plugin development is largely &amp;ldquo;download a bunch of files,
put them somewhere, hopefully have them organized correctly, and pray.&amp;rdquo; I aimed to fix that through the
creation of the tool I&amp;rsquo;m writing about today: &lt;a href=&#34;https://github.com/tmick0/sourceknight&#34; target=&#34;_blank&#34; &gt;sourceknight&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The core concept of sourceknight is to allow you to specify the dependencies of your project with a simple configuration file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;project&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;myplugin-example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;dependencies&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;sourcemod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;tar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1.10.0&lt;/span&gt;-&lt;span style=&#34;color:#ae81ff&#34;&gt;git6503&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;location&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;https://sm.alliedmods.net/smdrop/1.10/sourcemod-1.10.0-git6503-linux.tar.gz&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;unpack&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;source&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/addons&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;dest&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/addons&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;root&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/myplugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ae81ff&#34;&gt;example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, all you have to do is type &lt;code&gt;sourceknight build&lt;/code&gt;, and your plugins will magically be compiled.&lt;/p&gt;
&lt;p&gt;Maybe you aren&amp;rsquo;t writing your own plugins, but manage a server and hate the process of updating and compiling
all the plugins you use? Well, sourceknight can still help &amp;ndash; a sourceknight project doesn&amp;rsquo;t need to contain
any scripts of its own, and can also just be used to compile plugins you specify as dependencies.&lt;/p&gt;
&lt;p&gt;I did find a few other projects which alleged to help manage sourcemod based projects, however I believe
sourceknight, at least in vision, provides a much more streamlined experience. As of today, sourceknight
is mostly a proof of concept &amp;ndash; however, it is certainly capable of managing dependencies and compiling
plugins. I&amp;rsquo;m writing this post mostly in order to measure interest, find deficiencies, and identify what
features are desired moving forward. But nonetheless, hopefully someone will find it useful.&lt;/p&gt;
&lt;p&gt;If you missed the link above, the GitHub repository is here:
&lt;a href=&#34;https://github.com/tmick0/sourceknight&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/sourceknight&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Tools to check for compromised Keepass passwords</title>
      <link>https://lo.calho.st/posts/check-compromised-keepass/</link>
      <pubDate>Thu, 19 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/check-compromised-keepass/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s 2020, and by now everyone should know the basics of password hygiene: use strong passwords, don&amp;rsquo;t reuse them across sites, etc. A password manager such as &lt;a href=&#34;https://keepass.info/&#34; target=&#34;_blank&#34; &gt;Keepass&lt;/a&gt; is an essential tool for keeping track of all the passwords you inevitably end up with under this set of rules.&lt;/p&gt;
&lt;p&gt;Unfortunately, no matter how good your passwords are, there&amp;rsquo;s always a chance that they&amp;rsquo;ll get compromised. For example, a website might have a breach and someone can obtain your password regardless of how hard it would have been to guess. In cases like these, services like &lt;a href=&#34;https://haveibeenpwned.com&#34; target=&#34;_blank&#34; &gt;Have I Been Pwned&lt;/a&gt; can let you know this has happened. However, if your password is leaked in something like a large &lt;a href=&#34;https://en.wikipedia.org/wiki/Credential_stuffing&#34; target=&#34;_blank&#34; &gt;credential-stuffing&lt;/a&gt; dump, it might not be obvious which sites you may need to change your password on.&lt;/p&gt;
&lt;p&gt;For situations like this, Have I Been Pwned also provides &lt;a href=&#34;https://haveibeenpwned.com/Passwords&#34; target=&#34;_blank&#34; &gt;password lists&lt;/a&gt; which contain the cryptographic hashes of all known compromised passwords. However, you still can&amp;rsquo;t immediately identify which of your passwords have been breached.&lt;/p&gt;
&lt;p&gt;For this purpose, I have written a set of &lt;a href=&#34;https://github.com/tmick0/kdbxtools&#34; target=&#34;_blank&#34; &gt;very simple tools&lt;/a&gt; to dump hashes of your own Keepass-managed passwords and check them against the Have I Been Pwned list.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example using a database that only contains Keepass&amp;rsquo;s builtin example passwords.&lt;/p&gt;
&lt;p&gt;The first script simply dumps a CSV of your password hashes and some metadata:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ kdbx2csv ~/Desktop/Database.kdbx my_passwords.csv
Password: ****
$ cat my_passwords.csv 
8BE3C943B1609FFFBFC51AAD666D0A04ADF83C9D,User Name,Sample Entry
8CB2237D0679CA88DB6464EAC60DA96345513964,Michael321,Sample Entry #2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The second script then checks this CSV against the list from Have I Been Pwned:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ checkhashes my_passwords.csv pwned-passwords-sha1-ordered-by-hash-v7.txt 
8BE3C943B1609FFFBFC51AAD666D0A04ADF83C9D,136802,User Name,Sample Entry
8CB2237D0679CA88DB6464EAC60DA96345513964,2493390,Michael321,Sample Entry #2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that both of the example entries&amp;rsquo; passwords (&amp;ldquo;Password&amp;rdquo; and &amp;ldquo;12345&amp;rdquo;) are unsafe, occurring in breaches 136,802 and 2,493,390 times respectively.&lt;/p&gt;
&lt;p&gt;The code and further instructions are available on Github: &lt;a href=&#34;https://github.com/tmick0/kdbxtools&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/kdbxtools&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>A highly customizable RGB controller implementation for Arduino</title>
      <link>https://lo.calho.st/posts/rgb-arduino/</link>
      <pubDate>Sat, 28 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/rgb-arduino/</guid>
      <description>&lt;p&gt;I recently decided that I needed to add some color to my workspace, but didn&amp;rsquo;t want to just use any off-the-shelf RGB controller.
My first thought was to use an Arduino to control some RGB strips, but I didn&amp;rsquo;t want to have to to open the Arduino IDE and modify
the firmware every time I wanted to change the program.&lt;/p&gt;
&lt;p&gt;This desire eventually escalated to implementing a virtual machine on top of the Arduino with an application-specific instruction
set designed to easily manipulate LEDs.&lt;/p&gt;
&lt;p&gt;For the impatient reader, &lt;a href=&#34;https://github.com/tmick0/arduino-rgbctrl&#34; target=&#34;_blank&#34; &gt;here&amp;rsquo;s the Github link&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;controller-build&#34;&gt;Controller build&lt;/h1&gt;
&lt;p&gt;Though I did my initial testing with an Arduino Uno, I chose to go with 16MHz Pro Micros (like &lt;a href=&#34;https://www.sparkfun.com/products/12640&#34; target=&#34;_blank&#34; &gt;this one available from Sparkfun&lt;/a&gt;) for the actual
build in order to keep the footprint small. Other than that, the supporting circuitry is dependent on the application. I&amp;rsquo;ve deployed two controllers: one inside my PC, and one for backlight
behind my monitors. The setup I&amp;rsquo;m using for the PC is the simplest, so I&amp;rsquo;ll explain that one first.&lt;/p&gt;
&lt;p&gt;The controller inside my PC is driving an SK9822-based RGB strip, so it only needs a 5V supply. Given that it&amp;rsquo;s inside a PC, there is easy access to 5V via a Molex connector from the PSU.
Therefore, I&amp;rsquo;ve used that to power both the Arduino and the RGB strip. The controller module was built by soldering two female header rows to a proto board to accept the Arduino, wiring
the Molex to the RAW and GND pins, and attaching a JST socket to the data, clock, and power pins. It ended up fitting nicely in the back of my case and I was able to secure it with a Velcro
strap.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;pc-rgb-controller.jpg&#34; alt=&#34;Controller mounted inside a PC case&#34;&gt;&lt;/p&gt;
&lt;p&gt;(Sorry, I forgot to take a better photo before installing it.)&lt;/p&gt;
&lt;p&gt;The controller for my monitor backlight is a bit more involved. For this application, wanted an on/off switch as well as a few potentiometer inputs to control the color of the strip.
It&amp;rsquo;s running a WS2811 strip that takes 12V input, but I still needed a 5V reference and I didn&amp;rsquo;t want to have to keep the Arduino connected over USB. Therefore, I included a voltage
regulator to step down the 12V from the strip&amp;rsquo;s power supply and a MOSFET to handle the on/off capability (since I didn&amp;rsquo;t expect the latching pushbutton switch I was using to be rated
for much current).&lt;/p&gt;
&lt;p&gt;The result was still a reasonable size and fit nicely into a decently sized project box, with just the barrel jack for the 12V and a JST connector for the strip protruding from opposite sides.&lt;/p&gt;
&lt;p&gt;Since the circuit was a bit involved for me (having little to no experience doing such things), I drew up a schematic first.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;led-control-circuit.png&#34; alt=&#34;Schematic&#34;&gt;&lt;/p&gt;
&lt;p&gt;I then prototyped the design on a breadboard.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;breadboard-rgb-controller.jpg&#34; alt=&#34;Breadboard prototype&#34;&gt;&lt;/p&gt;
&lt;p&gt;I tested it out and everything worked smoothly. I decided to leave out the decoupling capacitor depicted in the schematic as I found it wasn&amp;rsquo;t necessary.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;breadboard-rgb-controller-on.jpg&#34; alt=&#34;Breadboard prototype driving an RGB strip&#34;&gt;&lt;/p&gt;
&lt;p&gt;For the final build, I ended up placing the potentiometers and the power switch on their own boards, connecting them to the main board with some jumpers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;rgb-controller-modules.jpg&#34; alt=&#34;Modular design in the case&#34;&gt;&lt;/p&gt;
&lt;p&gt;Testing it out prior to closing up the case, everything still worked as intended.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;rgb-final-test.jpg&#34; alt=&#34;Final test&#34;&gt;&lt;/p&gt;
&lt;p&gt;(Excuse the assorted debris on my workbench.)&lt;/p&gt;
&lt;p&gt;For completeness, the final product:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;desktop-rgb-controller.jpg&#34; alt=&#34;Desktop RGB controller&#34;&gt;&lt;/p&gt;
&lt;h1 id=&#34;virtual-machine&#34;&gt;Virtual machine&lt;/h1&gt;
&lt;p&gt;As I mentioned, the RGB controller itself is implemented in a virtual machine. It&amp;rsquo;s an 8 bit machine with 15 general purpose registers, and otherwise unremarkable except for a few
special instructions for analog input, RGB output, and HSV-to-RGB color space conversion. I wrote an assembler for the machine in Python so I could write code with mnemonics instead of
manually constructing bytecode.&lt;/p&gt;
&lt;p&gt;The program memory of the VM persists in the EEPROM of the Arduino and is read into RAM at startup. It can be reprogrammed over a simple serial protocol.&lt;/p&gt;
&lt;p&gt;The VM doesn&amp;rsquo;t have function calls (though there is branching) or any form of data memory; instead, programs only operate via I/O and registers. This was my first time implementing
a virtual machine, and I wanted to keep it simple.&lt;/p&gt;
&lt;h1 id=&#34;drivers&#34;&gt;Drivers&lt;/h1&gt;
&lt;p&gt;The VM uses three instructions to interact with RGB devices through a set of drivers that have been implemented in the firmware. Namely, there&amp;rsquo;s an &lt;code&gt;init&lt;/code&gt; instruction to activate a
driver, a &lt;code&gt;write&lt;/code&gt; instruction to store an RGB value to a buffer, and a &lt;code&gt;send&lt;/code&gt; instruction to activate the buffered values.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve implemented three drivers: a simple PWM-based driver for analog RGB, a driver for the WS281x family of RGB chips, and a driver for the APA102 family of RGB chips.&lt;/p&gt;
&lt;p&gt;Of the three, the WS281x driver was the most challenging, as it involved bitbanging the control signal with precise timings. Writing simple C or C++ code would not have met
the timing constraints, thus necessitating the use of inline assembly. I found a few implementations online, but they were all designed to have a hardcoded pin number known at
compile time. Since I wanted my driver to have the flexibility to choose an arbitrary pin, or even run multiple strips simultaneously, I needed to relearn AVR assembly and modify
the code to use a non-constant value. Other than that, everything went pretty smoothly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;send&lt;/code&gt; instruction ended up being implemented as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// get the IO port number for the requested pin
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; PORT_NUM &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; digitalPinToPort(driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// create a mask to enable toggling the correct pin
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; PORT_MASK &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; digitalPinToBitMask(driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// get the pointer to the IO port register
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;PORT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; portOutputRegister(PORT_NUM);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// disable interrupts to allow for precise timing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;cli();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// delay to trigger &amp;#34;data latch&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; micros();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ((micros() &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; t) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;50L&lt;/span&gt;) {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// pointer to the data buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;p &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// get the first value (subsequent values will be fetched in the asm)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;p&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// apply PORT_MASK to get the register value for the high/low states
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; high &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;PORT &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; PORT_MASK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; low &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;PORT &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;PORT_MASK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// tmp register will swap between high and low when need be
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; tmp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; low;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// the number of bits remaining in the current byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; nbits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// length of the output buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; nbytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ptr;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;asm&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;volatile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nextbit:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;st %a0, %1&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// store high
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sbrc %2, 7&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// don&amp;#39;t set tmp high if next bit zero
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mov  %4, %1&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// otherwise, do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dec  %3&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;// decrement bit counter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nop&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;           &lt;span style=&#34;color:#75715e&#34;&gt;// nop for keeping timing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;st   %a0, %4&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// write tmp to the IO register
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mov  %4, %5&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// next, we&amp;#39;ll set the line low
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;breq nextbyte&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// load the next byte if done with last
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rol  %2&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;// otherwise, rotate in the next bit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rjmp .+0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// nop for keeping timing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;st %a0, %5&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// set the line low
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rjmp .+0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// nop for keeping timing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nop&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;           &lt;span style=&#34;color:#75715e&#34;&gt;// 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rjmp nextbit&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// send the next bit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nextbyte:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ldi  %3, 8&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// reset the bit counter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ld   %2, %a6+&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// load the next byte
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;st %a0, %5&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// set the line low
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rjmp .+0&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;// nop for keeping timing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nop&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;           &lt;span style=&#34;color:#75715e&#34;&gt;// 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dec %7&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// decrement the byte counter
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;brne nextbit&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\t&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// loop if there are more bytes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;e&amp;#34;&lt;/span&gt;(PORT), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(high), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(val), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(nbits), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(tmp),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;r&amp;#34;&lt;/span&gt;(low), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;e&amp;#34;&lt;/span&gt;(p), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;w&amp;#34;&lt;/span&gt;(nbytes));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// re-enable interrupts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;sei();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// clear the output buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The assembly seen here is derived from &lt;a href=&#34;https://www.instructables.com/id/Bitbanging-step-by-step-Arduino-control-of-WS2811-/&#34; target=&#34;_blank&#34; &gt;this post&lt;/a&gt;. Note that the chip expects a GRB byte order. To keep the assembly simple, the reordering is handled in the &lt;code&gt;write&lt;/code&gt; instruction.&lt;/p&gt;
&lt;p&gt;The APA102 driver was much easier to implement, since it uses a clock signal and thus does not rely on precise timings.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; clock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;arg, data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;arg &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;digitalWrite&lt;/span&gt;(data, LOW);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// start frame
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// send data for each entry in the buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ptr; &lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// header (currently hardcoded max brightness)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0xff&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// the chip expects b, g, r byte order
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[i].b);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[i].g);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[i].r);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// end frame
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ptr; &lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;apa102_byte&lt;/span&gt;(clock, data, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// leave the line low when done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;digitalWrite&lt;/span&gt;(data, LOW);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;apa102_byte&lt;/code&gt; call simply sets the data line to the correct state then toggles the clock line high then low.&lt;/p&gt;
&lt;h1 id=&#34;instruction-set&#34;&gt;Instruction set&lt;/h1&gt;
&lt;p&gt;The VM implements 19 mnemonic instructions. In reality, the branch instructions are all implemented under a single opcode followed by a &lt;code&gt;mode&lt;/code&gt; flag which determines whether and how to inspect the &lt;code&gt;flag&lt;/code&gt; register.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mnemonic&lt;/th&gt;
&lt;th&gt;Operands&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nop&lt;/td&gt;
&lt;td&gt;[imm]&lt;/td&gt;
&lt;td&gt;no operation &amp;ndash; if imm specified and nonzero, sleep 2^(imm-1) ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set&lt;/td&gt;
&lt;td&gt;rdst (rsrc|imm)&lt;/td&gt;
&lt;td&gt;load register rdst from rsrc or immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;add&lt;/td&gt;
&lt;td&gt;rdst (rsrc|imm)&lt;/td&gt;
&lt;td&gt;add to rdst from rsrc or immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mul&lt;/td&gt;
&lt;td&gt;rdst (rsrc|imm)&lt;/td&gt;
&lt;td&gt;multiply rdst by rsrc or immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;div&lt;/td&gt;
&lt;td&gt;rdst (rsrc|imm)&lt;/td&gt;
&lt;td&gt;divide rdst by rsrc or immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mod&lt;/td&gt;
&lt;td&gt;rdst (rsrc|imm)&lt;/td&gt;
&lt;td&gt;modulo rdst by rsrc or immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cmp&lt;/td&gt;
&lt;td&gt;r0 (r1|imm)&lt;/td&gt;
&lt;td&gt;compare r0 to r1 or imm and store the result in flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;goto&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the instruction pointer to address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brne&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was not equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;breq&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brlt&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was less than&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brle&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was less or equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brgt&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was greater than&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;brge&lt;/td&gt;
&lt;td&gt;address&lt;/td&gt;
&lt;td&gt;move the ip to address if last comparison was greater or equal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hsv2rgb&lt;/td&gt;
&lt;td&gt;rh rs rv&lt;/td&gt;
&lt;td&gt;convert hsv values in registers to rgb (in place), each of hsv in [0, 255]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;init&lt;/td&gt;
&lt;td&gt;immd immc [immf]&lt;/td&gt;
&lt;td&gt;initialize output channel immc with driver number immd and optional flags immf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;td&gt;rr rg rb immc&lt;/td&gt;
&lt;td&gt;buffer rgb value from registers on output channel immc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;send&lt;/td&gt;
&lt;td&gt;immc&lt;/td&gt;
&lt;td&gt;activate the buffered output of immc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;rdst immpin&lt;/td&gt;
&lt;td&gt;load rdst with the value from the analog pin numbered by the immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;implementation&#34;&gt;Implementation&lt;/h1&gt;
&lt;h2 id=&#34;instructions&#34;&gt;Instructions&lt;/h2&gt;
&lt;p&gt;Instructions for &lt;code&gt;rgbvm&lt;/code&gt; are of varying size. Starting with a 4-bit opcode is the only thing they have in common.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_instruction {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_opcode opcode : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each class of instruction then has its own derived structure. For brevity, I&amp;rsquo;ll only include the structures of the arithmetic and driver interaction instructions, but this should be sufficient to provide a general idea of the structure of the bytecode.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_arithmetic_instruction {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_opcode opcode : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg dst : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg src : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; padding : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; imm[];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_init_instruction {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_opcode opcode : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; driver_type driver : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; channel : &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; arg : &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_write_instruction {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_opcode opcode : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg srcr : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg srcg : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg srcb : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; channel : &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; padding : &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_send_instruction {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_opcode : &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; channel : &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; padding : &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Arithmetic instructions have an optional immediate, thus the 0-length array at the end of that structure.&lt;/p&gt;
&lt;h2 id=&#34;vm-state-and-execution&#34;&gt;VM state and execution&lt;/h2&gt;
&lt;p&gt;The structure describing the state of the VM is simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_state {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// instruction pointer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; ip;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// length of code segment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; ip_max;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// 15 general purpose registers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; reg[&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// flag register
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; flag;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// output buffers
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_driver outputs[&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In &lt;code&gt;setup&lt;/code&gt;, the first two bytes of EEPROM are read to determine the length of the code segment (thus determining &lt;code&gt;ip_max&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt; code_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (EEPROM.read(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; (EEPROM.read(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The bytecode is then read from EEPROM into RAM:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; code_len; &lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;i) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s.code[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; EEPROM.read(i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint16_t&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In &lt;code&gt;loop&lt;/code&gt;, when executing code, the instruction at the current instruction pointer is passed to
&lt;code&gt;rgbvm_apply&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rgbvm_apply(delay, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;s.vm, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rgbvm_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;s.code[s.vm.ip]);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We pass a pointer to the &lt;code&gt;delay&lt;/code&gt; function so the VM can call it when a &lt;code&gt;nop&lt;/code&gt; instruction
with a nonzero argument is encountered.&lt;/p&gt;
&lt;p&gt;The implementation of &lt;code&gt;rgbvm_apply&lt;/code&gt; revolves around a switch on the opcode of the current instruction. For example, the arithmetic instructions are handled as follows.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_SET:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_ADD:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_MUL:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_DIV:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_MOD:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_CMP: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;dest;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; src;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgbvm_arith_op_impl op;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_arithmetic&lt;/span&gt;(vm,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_arithmetic_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)inst,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;dest, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;src, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;op, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;op&lt;/span&gt;(vm, dest, src);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_increment_ip&lt;/span&gt;(vm, size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; RGBVM_STATUS_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These instructions are all implemented as functions taking arguments of a pointer to the VM state, a pointer to the destination operand, and  the value of the source operand. The &lt;code&gt;rgbvm_decode_arithmetic&lt;/code&gt; function is decoding the operands and setting the corresponding pointer and value, as well as setting a function pointer to the instruction implementation and determining the length of that instruction.&lt;/p&gt;
&lt;p&gt;Decoding the destination operand simply involves returning a pointer to the correct register, while decoding the source operand also has logic handling immediate values.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_val&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_state &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;vm, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rgbvm_reg reg,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_arithmetic_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;inst,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;dest, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;imm_flag) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (reg &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; RGBVM_REG_IM) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;dest &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; inst&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;imm[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;imm_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;imm_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;src &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_reg&lt;/span&gt;(vm, reg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;dest &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;src;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;imm_flag&lt;/code&gt; allows &lt;code&gt;rgbvm_decode_arithmetic&lt;/code&gt; to determine the length of the instruction and is used to increment the instruction pointer appropriately.&lt;/p&gt;
&lt;p&gt;The instructions for communicating with the drivers rely on a set of functions with a common interface for initializing, writing, and outputting data.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;void&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;driver_write)(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_driver &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;void&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;driver_init)(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_driver &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;void&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;driver_send)(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_driver &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each driver defines an &lt;code&gt;init&lt;/code&gt; function which sets pointers to &lt;code&gt;write&lt;/code&gt; and &lt;code&gt;send&lt;/code&gt; within the VM&amp;rsquo;s &lt;code&gt;outputs&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;The corresponding instructions are implemented as follows.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_INIT: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_init_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_init_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)inst;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver_init init &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_driver&lt;/span&gt;(i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;driver);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;outputs[i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;channel], i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_increment_ip&lt;/span&gt;(vm, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; RGBVM_STATUS_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_WRITE: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_write_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_write_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)inst;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_reg&lt;/span&gt;(vm, i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;srcr);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;g &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_reg&lt;/span&gt;(vm, i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;srcg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_decode_reg&lt;/span&gt;(vm, i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;srcb);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver_write fn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;outputs[i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;channel].write;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;outputs[i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;channel], &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;r, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;g, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;b);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_increment_ip&lt;/span&gt;(vm, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; RGBVM_STATUS_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; RGBVM_OP_SEND: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_send_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rgbvm_send_instruction &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)inst;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    driver_send fn &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;outputs[i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;channel].send;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fn&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;outputs[i&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;channel]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;rgbvm_increment_ip&lt;/span&gt;(vm, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; RGBVM_STATUS_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The drivers implement &lt;code&gt;write&lt;/code&gt; by storing the &lt;code&gt;r, g, b&lt;/code&gt; values in the output channel&amp;rsquo;s buffer; &lt;code&gt;send&lt;/code&gt; then implements the protocol for sending this data to the LEDs.&lt;/p&gt;
&lt;p&gt;The branch instructions follow a similar pattern to the arithmetic instructions. These and the remaining instructions&amp;rsquo; implementations are omitted for brevity&lt;/p&gt;
&lt;h2 id=&#34;serial-protocol&#34;&gt;Serial protocol&lt;/h2&gt;
&lt;p&gt;The state machine handling bytecode updates from serial ingests a single byte at a time.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c++&#34; data-lang=&#34;c++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (Serial.available() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    proto_msg msg;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (proto_state_machine_ingest(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;s.vm, s.code, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;s.psm, Serial.read(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                   &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;msg, write_eeprom) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Serial.write((&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt;)msg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The return of &lt;code&gt;proto_state_machine_ingest&lt;/code&gt; indicates whether &lt;code&gt;msg&lt;/code&gt; has been modified, thereby
indicating a response that should be written to serial.&lt;/p&gt;
&lt;p&gt;The state machine itself supports four states:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; proto_state {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  PROTO_STATE_INIT,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  PROTO_STATE_SIZEH,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  PROTO_STATE_SIZEL,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  PROTO_STATE_CODE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Initially, it is in the &lt;code&gt;INIT&lt;/code&gt; state. When in any other state, the VM pauses execution of bytecode until the transaction is completed.&lt;/p&gt;
&lt;p&gt;The state machine first expects a &lt;code&gt;HELLO&lt;/code&gt; message from the peer; upon receipt it transitions to the &lt;code&gt;SIZEL&lt;/code&gt; state, indicating that it is ready to receive the low byte of the length of the bytecode.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PROTO_STATE_INIT: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (byte &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; PROTO_MSG_HELLO) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_STATE_SIZEL;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;res &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_MSG_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;res &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_MSG_ERR;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that byte is received, the state is  set to &lt;code&gt;SIZEH&lt;/code&gt;, indicating that the high byte of the bytecode length is expected. After receiving that, the state machine then transitions to &lt;code&gt;CODE&lt;/code&gt;, where it stays until the specified number of bytes are received.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PROTO_STATE_SIZEL: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ip_max)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; byte;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_STATE_SIZEH;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PROTO_STATE_SIZEH: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ((&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ip_max)[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; byte;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_STATE_CODE;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;offset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;res &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_MSG_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The serial ingest implementation writes the bytecode directly to the VM&amp;rsquo;s &lt;code&gt;code&lt;/code&gt;
array in order to save memory. After receiving the full bytecode, the state machine returns
to &lt;code&gt;INIT&lt;/code&gt; and returns an &lt;code&gt;OK&lt;/code&gt; message.&lt;/p&gt;
&lt;p&gt;After receiving a bytecode update, a callback is invoked to write the new bytecode to EEPROM, then the VM state is updated with the new length of the code segment and the instruction pointer is reset to zero.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PROTO_STATE_CODE: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    code[psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;offset&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; byte;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;offset &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ip_max) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;cb&lt;/span&gt;(psm);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ip &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        psm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_STATE_INIT;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;res &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PROTO_MSG_OK;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because writing the bytecode to EEPROM is delayed until after receiving the full stream, the device can simply be reset to revert back to the old code if an error occurs in transmission.&lt;/p&gt;
&lt;h1 id=&#34;outcome&#34;&gt;Outcome&lt;/h1&gt;
&lt;p&gt;This was a challenging project for me because it involved a lot of new things. However, I learned a lot and it payed off. After about a week of working on this for a couple of hours every day, I&amp;rsquo;ve finally got a colorful battlestation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;rgb-outcome.jpg&#34; alt=&#34;RGB workspace&#34;&gt;&lt;/p&gt;
&lt;p&gt;The source code for this project is &lt;a href=&#34;https://github.com/tmick0/arduino-rgbctrl&#34; target=&#34;_blank&#34; &gt;available on Github&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Timelapses from Landsat data</title>
      <link>https://lo.calho.st/posts/landsat-timelapses/</link>
      <pubDate>Sun, 08 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/landsat-timelapses/</guid>
      <description>&lt;p&gt;I thought it would be cool to extend the scripts I posted about yesterday with the ability to create
a timelapse.&lt;/p&gt;


&lt;video controls autoplay=&#34;true&#34; loop=&#34;true&#34;&gt;
    &lt;source src=&#34;landsat_timelapse.mp4&#34; type=&#34;video/mp4&#34;/&gt;
&lt;/video&gt;

&lt;p&gt;The above video shows all of the Landsat 8 scenes of WRS cell 33/36 over the course of 2019.&lt;/p&gt;
&lt;p&gt;The repository has been updated with the code to generate this: &lt;a href=&#34;http://github.com/tmick0/landsat_fetch&#34; target=&#34;_blank&#34; &gt;http://github.com/tmick0/landsat_fetch&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>An automated workflow for fetching and mosaicing Landsat imagery</title>
      <link>https://lo.calho.st/posts/landsat-workflow/</link>
      <pubDate>Sat, 07 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/landsat-workflow/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;pansharpened_false_color.jpg&#34; alt=&#34;A false color image of Albuquerque&#34;&gt;&lt;/p&gt;
&lt;p&gt;One extension I had been planning for my previous project involving
&lt;a href=&#34;https://lo.calho.st/posts/printing-terrain-meshes&#34; &gt;terrain meshes&lt;/a&gt;
was the addition of satellite imagery to add color to the models.
&lt;a href=&#34;https://landsat.gsfc.nasa.gov/landsat-8/landsat-8-overview/&#34; target=&#34;_blank&#34; &gt;Landsat&lt;/a&gt;,
in addition to enabling important climatology research and other science missions, turned out to be a great
source of open data to use for this.&lt;/p&gt;
&lt;p&gt;As a step toward integrating this data with my terrain mesh generator, I&amp;rsquo;ve implemented
an automated workflow for fetching, radiometrically correcting, and mosaicing these images.&lt;/p&gt;
&lt;p&gt;AWS generously provides free access to &lt;a href=&#34;https://registry.opendata.aws/landsat-8/&#34; target=&#34;_blank&#34; &gt;its S3-based Landsat mirror&lt;/a&gt;,
however the &lt;a href=&#34;https://landsat.gsfc.nasa.gov/the-worldwide-reference-system/&#34; target=&#34;_blank&#34; &gt;Worldwide Reference System&lt;/a&gt;
Landsat uses to index products makes it a bit less than straightforward to identify the right data
to fetch. Therefore, I implemented a parser for the provided scene list that resolves requested
geodetic extents to a set of footprints which cover them. The script then fetches all of the data
needed to generate the requested output product.&lt;/p&gt;
&lt;p&gt;After obtaining the data, a bit of massaging is required to make it useful. The data has to be
&lt;a href=&#34;https://en.wikipedia.org/wiki/Radiometric_calibration#Satellite_sensor_calibration&#34; target=&#34;_blank&#34; &gt;radiometrically calibrated&lt;/a&gt;
and then warped to a more flexible map projection.&lt;/p&gt;
&lt;p&gt;Finally, I&amp;rsquo;ve implemented a &lt;a href=&#34;https://en.wikipedia.org/wiki/Pansharpened_image&#34; target=&#34;_blank&#34; &gt;pansharpening&lt;/a&gt; operation
which uses Landsat 8&amp;rsquo;s panchromatic band to improve the spatial resolution of its spectral bands and produce
a high-fidelity output product.&lt;/p&gt;
&lt;p&gt;The code that handles all of this has been &lt;a href=&#34;https://github.com/tmick0/landsat_fetch&#34; target=&#34;_blank&#34; &gt;released to Github&lt;/a&gt; for all to use.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Generating terrain meshes for 3D printing</title>
      <link>https://lo.calho.st/posts/printing-terrain-meshes/</link>
      <pubDate>Sat, 05 Oct 2019 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/printing-terrain-meshes/</guid>
      <description>&lt;p&gt;A while back, I thought 3D printed models of the local terrain might be a cool gift idea. To make this a reality, I have implemented a simple Python utility to convert publicly-available terrain data into a format suitable for 3D printing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;mesh_example.png&#34; alt=&#34;example topographic mesh&#34;&gt;&lt;/p&gt;
&lt;p&gt;Most 3D printers expect STL models, which define a solid in terms of vertices in 3D Cartesian space and faces (triangles) which connect them. However, terrain models are typically distributed as rasters of heights indexed by latitudes and longitudes. Fortunately, libraries exist to convert latitude, longitude, altitude tuples to standardized Cartesian coordinates. However, some additional massaging of the data is required.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in the details of this process, read on. Otherwise, &lt;a href=&#34;https://github.com/tmick0/topostl&#34; target=&#34;_blank&#34; &gt;the project is available on Github&lt;/a&gt; for immediate use, without you needing to worry about it.&lt;/p&gt;
&lt;h2 id=&#34;getting-the-data&#34;&gt;Getting the data&lt;/h2&gt;
&lt;p&gt;Before we can start, we need to find terrain data to work with. Fortunately, NASA has been kind enough to make its &lt;a href=&#34;https://lpdaac.usgs.gov/products/srtmgl3v003/&#34; target=&#34;_blank&#34; &gt;SRTMGL3 dataset&lt;/a&gt; public, which gives us fairly high-quality terrain data for (mostly) the entire world. I don&amp;rsquo;t know how the radar magic that they used to create these models works, but fortunately the form they are distributed in is fairly easy to work with.&lt;/p&gt;
&lt;p&gt;Each raster, or &amp;ldquo;granule,&amp;rdquo; covers a square measuring 1 degree of latitude and 1 degree of longitude. At the 3 arcsecond resolution we&amp;rsquo;ll be using, this gives us a raster of 1201 by 1201 pixels. The data is stored as a matrix of big-endian signed shorts without any sort of metadata, and thus we can read it with a simple one-liner in numpy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;altitudes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromfile(filename, dtype&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;gt;i2&amp;#39;&lt;/span&gt;, count&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1201&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1201&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;reshape((&lt;span style=&#34;color:#ae81ff&#34;&gt;1201&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1201&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we&amp;rsquo;ve got a matrix representing the altitudes associated with the latitude and longitude of each pixel. The latitudes and longitudes themselves are determined by the pixel coordinates as well as the filename. The filename itself reveals the latitude and longitude of the bottom-left (southwest) corner of the raster; each pixel is 3 arcseconds by 3 arcseconds in size.&lt;/p&gt;
&lt;p&gt;Again, we can use a few simple numpy incantations to give us arrays of latitudes and longitudes that we can index in parallel with the altitudes we extracted:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lats &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linspace(north_latitude, south_latitude, num_rows, &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lons &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linspace(west_longitude, east_longitude, num_cols, &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;longrid, latgrid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;meshgrid(lons, lats)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the region we want to 3D print lies entirely within one SRTM granule, this is almost all we have to do. However, we must cover an edge case where the target area spans more than a single raster.&lt;/p&gt;
&lt;h2 id=&#34;merging-granules-and-extracting-the-target-area&#34;&gt;Merging granules and extracting the target area&lt;/h2&gt;
&lt;p&gt;Unless we&amp;rsquo;re 3D printing an area that itself spans more than 1 square degree (which I wouldn&amp;rsquo;t recommend due to the limited visibility of terrain detail at this size), the worst case scenario we will encounter requires us to merge four adjacent rasters.&lt;/p&gt;
&lt;p&gt;If we determine that the requested area spans more than one raster, we simply load all of the required rasters and merge them as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;merged &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;block(blocks)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where blocks is a 2D list representing the physical layout of the rasters we are merging, and each element is a loaded raster. It is important to note that you must trim off the first row and last column in this step, as they are overlapped with the adjacent granules.&lt;/p&gt;
&lt;p&gt;Now that we have aggregated the granules of interest, we can extract the region we want:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1200&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; int((north &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; floor(north)) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1200&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;c0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int((west &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; floor(west)) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1200&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rows &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int((north &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; south) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cols &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int((east &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; west) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; merged[r0:r0&lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;rows, c0:c0&lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;cols]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, we can also apply a scaling factor to the altitudes in order to exaggerate the altitudes and make the 3D printed result more interesting to look at.&lt;/p&gt;
&lt;h2 id=&#34;converting-to-cartesian-coordinates&#34;&gt;Converting to Cartesian coordinates&lt;/h2&gt;
&lt;p&gt;At this stage, we have extracted vectors of altitudes, latitudes, and longitudes corresponding to the data we wish to 3D print. However, we must combine these three parallel vectors into a coherent set of 3D vertices.&lt;/p&gt;
&lt;p&gt;Latitudes and longitudes are ellipsoidal coordinates that give us a point on the surface of the Earth, as opposed to a point on a plane. While it is reasonable to exploit the local linearity of the surface of the ellipsoid and use simple scaling factors to generate a set of vertices above a fixed plane, I actually found it easier to use the &amp;ldquo;real&amp;rdquo; conversion between ellipsoidal and Cartesian coordinates for this step.&lt;/p&gt;
&lt;p&gt;I found that the most convenient coordinate system to use was ECEF, or Earth Centered Earth Fixed. This is a simple 3D Cartesian system where the origin (0, 0, 0) is located at the center of the Earth, and vectors are expressed in meters relative to this.&lt;/p&gt;
&lt;p&gt;I used the Python library pyproj to perform the conversion as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ECEF &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pyproj&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Proj(proj&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;geocent&amp;#39;&lt;/span&gt;, ellps&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;WGS84&amp;#39;&lt;/span&gt;, datum&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;WGS84&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LLA &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pyproj&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Proj(proj&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;latlong&amp;#39;&lt;/span&gt;, ellps&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;WGS84&amp;#39;&lt;/span&gt;, datum&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;WGS84&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x, y, z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pyproj&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;transform(LLA, ECEF, longrid, latgrid, altitudes, radians&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we have three parallel arrays representing our vertices.&lt;/p&gt;
&lt;h2 id=&#34;correcting-orientation-positioning-and-scale&#34;&gt;Correcting orientation, positioning, and scale&lt;/h2&gt;
&lt;p&gt;Unfortunately, the ellipsoidal to ECEF conversion essentially gives us a patch somewhere on the surface of the Earth, and thus it is not oriented correctly for 3D printing. To correct the orientation, I exploited some facts about the geometry of the Earth ellipsoid the ECEF coordinate system.&lt;/p&gt;
&lt;p&gt;First, we must ensure that the model is &amp;ldquo;right side up.&amp;rdquo; We can use an ellipsoidal normal vector to approximate &amp;ldquo;up&amp;rdquo; and rotate our vertices such that this vector aligns with the z-axis. To avoid re-learning geometry, I simply used pyproj to find the axis:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_axis&lt;/span&gt;(lat0, lon0, alt0, lat1, lon1, alt1):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    v0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pyproj&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;transform(LLA, ECEF, lon0, lat0, alt0, radians&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    v1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pyproj&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;transform(LLA, ECEF, lon1, lat1, alt1, radians&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array(v1) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array(v0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;z_axis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_axis(center_lat, center_lon, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;, center_lat, center_lon, &lt;span style=&#34;color:#ae81ff&#34;&gt;1000.&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I did something similar to align the north and south edges of the model relative to the x axis:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x_axis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_axis(south, west, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;, south, east, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we can generate rotation matrices and use the Scipy spatial library to perform the rotation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_rotation&lt;/span&gt;(A, B):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    au &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; A &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linalg&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;norm(A)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bu &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; B &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;linalg&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;norm(B)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    R &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array([[bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  [bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  [bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;], bu[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;au[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]]])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; scipy&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;spatial&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;transform&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Rotation&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_dcm(R)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_rotation(z_axis, [&lt;span style=&#34;color:#ae81ff&#34;&gt;1.&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_rotation(r0&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;apply(x_axis), [&lt;span style=&#34;color:#ae81ff&#34;&gt;1.&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r1 &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; r0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vertices &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;apply(vertices)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our vertices are now oriented above the x-y plane in a way that kind of makes sense. However, the data is still &amp;ldquo;life size&amp;rdquo; and must be scaled down. First, we must center them around the origin in x-y space:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x, _, z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mean(vertices, axis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_, y, _ &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vertices&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;min(axis&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vertices &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;subtract(vertices, [x, y, z])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, they can be scaled:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vertices &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vertices &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; scaling_factor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last step is now to create the faces connecting these vertices, and our 3D model will be complete.&lt;/p&gt;
&lt;h2 id=&#34;creating-the-solid&#34;&gt;Creating the solid&lt;/h2&gt;
&lt;p&gt;Despite the amount of math we saw in the previous section, I found this step to be the most difficult. Building faces over the vertices along the &amp;ldquo;top&amp;rdquo; of the model, where the terrain is, was relatively straightforward; however, making the model solid at the sides and bottom was a bit trickier.&lt;/p&gt;
&lt;p&gt;At this point, it&amp;rsquo;s been long enough since I wrote the code that I don&amp;rsquo;t remember what exactly I was doing. To make things worse, it would appear that I didn&amp;rsquo;t write any comments. Therefore, all I can do is &lt;a href=&#34;https://github.com/tmick0/topostl/blob/master/topostl/normalize.py#L47&#34; target=&#34;_blank&#34; &gt;link the relevant section of code&lt;/a&gt; and wish you the best, if you wish to attempt understanding it.&lt;/p&gt;
&lt;h3 id=&#34;exporting-the-mesh&#34;&gt;Exporting the mesh&lt;/h3&gt;
&lt;p&gt;At this point, we are ready to export our mesh as an STL file. This is yet another relatively straightforward file format, consisting of a mostly-useless header and a list of vertices that form each face. Each face also has some optional auxiliary information and a normal vector, however I found that populating this was unnecessary. Using Python&amp;rsquo;s builtin struct module, we can easily pack our floating-point coordinates into the file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(&lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\0&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;I&amp;#39;&lt;/span&gt;, len(triangles)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; a, b, c &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; triangles:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;fff&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;fff&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;vertices[a]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;fff&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;vertices[b]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;fff&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;vertices[c]))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fh&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(struct&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;lt;H&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we&amp;rsquo;re finally ready to print. However, not having my own 3D printer, I ended up sending the model over to Shapeways. It turned out pretty great:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;20191005_165223.jpg&#34; alt=&#34;3d printed result&#34;&gt;&lt;/p&gt;
&lt;p&gt;Again, the code is available here on Github: &lt;a href=&#34;https://github.com/tmick0/topostl&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/topostl&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>A graphical tool for configuring Alesis V-Series MIDI controllers on Linux.</title>
      <link>https://lo.calho.st/posts/alesis-v-config-gui/</link>
      <pubDate>Sun, 12 Nov 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/alesis-v-config-gui/</guid>
      <description>&lt;p&gt;In my &lt;a href=&#34;https://lo.calho.st/posts/reverse-engineering-sysex&#34; &gt;last post&lt;/a&gt;, I explained how I reverse-engineered the Alesis SysEx protocol and detailed my findings. Now, two months later, I&amp;rsquo;ve finally decided to implement a tool for configuring these controllers on Linux.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-11-12-13-47-14.png&#34; alt=&#34;screenshot of the application&#34;&gt;&lt;/p&gt;
&lt;p&gt;The entirety of this work was done over the past two days, so it likely contains some hidden bugs, but as far as I can tell it is entirely usable.&lt;/p&gt;
&lt;p&gt;Unfortunately, this is my first attempt at writing any kind of GUI, so it&amp;rsquo;s not beautiful. But hey, it works.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;https://github.com/tmick0/alesisvsysex&#34; target=&#34;_blank&#34; &gt;find the project on GitHub&lt;/a&gt;. Contributions are welcome.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Reverse engineering the Alesis V-series SysEx protocol.</title>
      <link>https://lo.calho.st/posts/reverse-engineering-sysex/</link>
      <pubDate>Fri, 08 Sep 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/reverse-engineering-sysex/</guid>
      <description>&lt;p&gt;I recently got back into music production and decided to order myself a MIDI controller. I got a few recommendations for the the &lt;a href=&#34;https://www.amazon.com/Alesis-V25-Keyboard-Controller-Buttons/dp/B00IWWBSD6/?th=1&#34; target=&#34;_blank&#34; &gt;Alesis V25&lt;/a&gt;, so I went ahead and ordered it. However, I was less than pleased to find that its configuration software wouldn&amp;rsquo;t run on Linux, even under Wine. Of course, this prompted me to reverse engineer the protocol that lets the software talk to the keyboard.&lt;/p&gt;
&lt;h2 id=&#34;overview-of-midi&#34;&gt;Overview of MIDI&lt;/h2&gt;
&lt;p&gt;Musical Instrument Digital Interface (MIDI) is a long-lived standard for interactions both among musical instruments and between instruments and software. The Alesis V25 is a MIDI controller, which is essentially just a keyboard that lets you send notes to a Digital Audio Workstation (DAW) such as &lt;a href=&#34;https://www.bitwig.com/&#34; target=&#34;_blank&#34; &gt;Bitwig&lt;/a&gt; (which is just my personal favorite).&lt;/p&gt;
&lt;p&gt;MIDI messages are always three bytes, and can look something like this (in hexadecimal):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;09 30 6a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first byte is called the &amp;ldquo;status byte,&amp;rdquo; and the second two are simply called &amp;ldquo;data bytes.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The meaning of the particular bytes in the above message are as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;09&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Channel 0, note on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;30&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Note 48 (C#4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;6a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Velocity 106&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In addition to note messages, there are also Control Change (CC) messages which indicate that a knob was turned or a button was pressed. However, these standard messages aren&amp;rsquo;t going to be very important for the adventure we&amp;rsquo;re going on today. Reconfiguring MIDI devices uses an extended protocol called SysEx.&lt;/p&gt;
&lt;h2 id=&#34;introduction-to-sysex&#34;&gt;Introduction to SysEx&lt;/h2&gt;
&lt;p&gt;System Exclusive (SysEx) is an extension to MIDI that lets instrument manufacturers define custom commands for their devices. The specification is quite simple: it only requires that SysEx messages start with a two byte header, and end with a one-byte trailer:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;f0 xx ... f7
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;f0&lt;/code&gt; is the start byte, &lt;code&gt;xx&lt;/code&gt; is a placeholder for a manufacturer ID, and &lt;code&gt;f7&lt;/code&gt; is the end byte. The bytes in between can be anything, as long as their high bits aren&amp;rsquo;t set (i.e., they are less than or equal to &lt;code&gt;7f&lt;/code&gt;). Their meaning is interpreted on a manufacturer-by-manufacturer basis.&lt;/p&gt;
&lt;h2 id=&#34;overview-of-the-alesis-v25-editor&#34;&gt;Overview of the Alesis V25 Editor&lt;/h2&gt;
&lt;p&gt;Alesis&amp;rsquo; editor lets you do things such as reassign the control numbers on the keyboard&amp;rsquo;s knobs and buttons. It doesn&amp;rsquo;t do much &amp;ndash; it mostly just supports reading the configuration from the keyboard, editing it, and writing it back. The interface looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-09-08-22-14-58.png&#34; alt=&#34;screenshot of the official alesis configuration application&#34;&gt;&lt;/p&gt;
&lt;p&gt;Since the software doesn&amp;rsquo;t work on Linux, I had it running in a virtual Windows machine under Virtualbox, with USB forwarding enabled for the device.&lt;/p&gt;
&lt;h2 id=&#34;intercepting-sysex-messages&#34;&gt;Intercepting SysEx Messages&lt;/h2&gt;
&lt;p&gt;We want to see what this software is doing in the backend to update the MIDI device&amp;rsquo;s configuration. To do that, we&amp;rsquo;ll need to snoop on the SysEx messages that it exchanges with the device.&lt;/p&gt;
&lt;p&gt;It turns out that the popular network-capture software &lt;a href=&#34;https://www.wireshark.org&#34; target=&#34;_blank&#34; &gt;Wireshark&lt;/a&gt; supports snooping on USB devices. That&amp;rsquo;s exactly what we&amp;rsquo;ll need to do today.&lt;/p&gt;
&lt;p&gt;First, I had to enable the &lt;code&gt;usbmon&lt;/code&gt; kernel module:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo modprobe usbmon
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, I started up Wireshark and started listening for packets. The display was initially quite noisy due to other USB devices (mostly my mouse). I found the bus and device IDs via &lt;code&gt;lsusb&lt;/code&gt; and filtered on those.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-09-08-22-26-48.png&#34; alt=&#34;screenshot of captured communication in wireshark&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this particular capture I&amp;rsquo;ve simply queried for the controller&amp;rsquo;s current configuration and received a reply. The 80-byte packet is the query, and the 128-byte packets are chunks of the reply. The 64-byte messages in between appear to be acknowledgements occurring at the USB protocol level.&lt;/p&gt;
&lt;p&gt;We can dissect one of the packets to see what&amp;rsquo;s going on:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-09-08-22-30-10.png&#34; alt=&#34;dissected sysex packet&#34;&gt;&lt;/p&gt;
&lt;p&gt;We can see that the SysEx message has been divided up into 3-byte segments in accordance with the traditional MIDI specification. At the USB level, each 3-byte segment is given a header to indicate which device it is from and how many of the SysEx bytes are meaningful. The details of that aren&amp;rsquo;t important, but the result is that we are able to pull out the actual SysEx message for the query:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;f0 00 00 0e 00 41 62 00 5d f7
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can identify the SysEx start and end bytes, and conveniently the manufacturer ID is &lt;code&gt;00&lt;/code&gt;. A bit of research has driven me toward the conclusion that because Alesis hasn&amp;rsquo;t officially registered a MIDI manufacturer ID, it uses 00 with a two-byte extension of &lt;code&gt;00 0e&lt;/code&gt;. I haven&amp;rsquo;t deciphered all of this message, however have been able to determine that the byte &lt;code&gt;62&lt;/code&gt; is the important part; it indicates that this packet is a query for the current configuration.&lt;/p&gt;
&lt;h2 id=&#34;decoding-the-reply&#34;&gt;Decoding the Reply&lt;/h2&gt;
&lt;p&gt;To determine which part of the SysEx messages do what, I had to play around with the GUI for a while, changing options to see how they affected the data going over the wire. This was a tedious process, however in the end it only took about three hours.&lt;/p&gt;
&lt;p&gt;The reply is quite long, and is split over several USB packets, however I&amp;rsquo;ve copied and annotated the final SysEx message here:&lt;/p&gt;


&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
    &lt;th&gt;Raw bytes&lt;/th&gt;
    &lt;th&gt;Section&lt;/th&gt;
    &lt;th&gt;Interpretation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;f0&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;SysEx start byte&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;00 00 0e&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Alesis manufacturer ID&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;00 41 63 00 5d&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Some sort of header&lt;/td&gt;
    &lt;td&gt;The 3rd byte indicates message type:
        &lt;ul&gt;
            &lt;li&gt;&lt;code&gt;61&lt;/code&gt;: Set configuration&lt;/li&gt;
            &lt;li&gt;&lt;code&gt;62&lt;/code&gt;: Query configuration&lt;/li&gt;
            &lt;li&gt;&lt;code&gt;63&lt;/code&gt;: Current configuration (reply)&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;0c 02 00 00&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Keys configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Base note&lt;/li&gt;
            &lt;li&gt;Current octave&lt;/li&gt;
            &lt;li&gt;Channel&lt;/li&gt;
            &lt;li&gt;Velocity curve&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;00&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Pitch wheel configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Channel&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;00 01 00 7f&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Mod wheel configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Channel&lt;/li&gt;
            &lt;li&gt;CC number&lt;/li&gt;
            &lt;li&gt;Minimum value&lt;/li&gt;
            &lt;li&gt;Maximum value&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
    &lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;40 00 7f 00&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;Sustain pedal configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;CC number&lt;/li&gt;
            &lt;li&gt;Minimum value&lt;/li&gt;
            &lt;li&gt;Maximum value&lt;/li&gt;
            &lt;li&gt;Channel&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;
        &lt;code&gt;00 14 00 7f 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt; 00 15 00 7f 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt; 00 16 00 7f 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt; 00 17 00 7f 00&lt;/code&gt;
    &lt;/td&gt;
    &lt;td&gt;Knobs configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Operation mode
                &lt;ul&gt;
                    &lt;li&gt;&lt;code&gt;00&lt;/code&gt;: CC&lt;/li&gt;
                    &lt;li&gt;&lt;code&gt;01&lt;/code&gt;: Aftertouch&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/li&gt;
            &lt;li&gt;CC number&lt;/li&gt;
            &lt;li&gt;Minimum value&lt;/li&gt;
            &lt;li&gt;Maximum value&lt;/li&gt;
            &lt;li&gt;Channel&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;
        &lt;code&gt;00 31 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 20 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 2a 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 2e 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 24 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 25 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 26 00 00 09&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 27 00 00 09&lt;/code&gt;
    &lt;/td&gt;
    &lt;td&gt;Pads configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Operation mode
                &lt;ul&gt;
                    &lt;li&gt;&lt;code&gt;00&lt;/code&gt;: Note&lt;/li&gt;
                    &lt;li&gt;&lt;code&gt;01&lt;/code&gt;: Toggle CC&lt;/li&gt;
                    &lt;li&gt;&lt;code&gt;02&lt;/code&gt;: Momentary CC&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/li&gt;
            &lt;li&gt;Note / CC number&lt;/li&gt;
            &lt;li&gt;Fixed (?) / Minimum CC value&lt;/li&gt;
            &lt;li&gt;Velocity curve / Maximum CC value&lt;/li&gt;
            &lt;li&gt;Channel&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;
        &lt;code&gt;00 30 7f 00 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 31 7f 00 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 32 7f 00 00&lt;/code&gt;&lt;br/&gt;
        &lt;code&gt;00 33 7f 00 00&lt;/code&gt;
    &lt;/td&gt;
    &lt;td&gt;Buttons configuration&lt;/td&gt;
    &lt;td&gt;
        &lt;ol&gt;
            &lt;li&gt;Operation mode
                &lt;ul&gt;
                    &lt;li&gt;&lt;code&gt;00&lt;/code&gt;: Toggle&lt;/li&gt;
                    &lt;li&gt;&lt;code&gt;01&lt;/code&gt;: Momentary&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/li&gt;
            &lt;li&gt;CC number&lt;/li&gt;
            &lt;li&gt;On value&lt;/li&gt;
            &lt;li&gt;Off value&lt;/li&gt;
            &lt;li&gt;Channel&lt;/li&gt;
        &lt;/ol&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;td&gt;&lt;code&gt;f7&lt;/code&gt;&lt;/td&gt;
    &lt;td&gt;SysEx end byte&lt;/td&gt;
    &lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Note that I&amp;rsquo;ve neglected exploring some of the options provided by the GUI, such as assigning the buttons to Program Change events. I also have no idea what the &amp;ldquo;Fixed&amp;rdquo; field does for the drum pads, as the word &amp;ldquo;Fixed&amp;rdquo; is the only description offered by the GUI. I suspect it enables fixed velocity, but I haven&amp;rsquo;t bothered to check.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Now that I&amp;rsquo;ve decoded the SysEx protocol, I plan to make a tool to enable editing the controller&amp;rsquo;s configuration under Linux. I may go as far as writing a controller script to enable modifying it from within Bitwig. Check for updates here to see what&amp;rsquo;s to come.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Using black magic to make a fast circular buffer.</title>
      <link>https://lo.calho.st/posts/black-magic-buffer/</link>
      <pubDate>Tue, 15 Aug 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/black-magic-buffer/</guid>
      <description>&lt;p&gt;Yesterday, I took a glance at &lt;a href=&#34;https://en.wikipedia.org/wiki/Circular_buffer&#34; target=&#34;_blank&#34; &gt;the Wikipedia page for the circular buffer&lt;/a&gt; and was intrigued by an alleged optimization technique that I was not familiar with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A circular-buffer implementation may be optimized by mapping the underlying buffer to two contiguous regions of virtual memory. (Naturally, the underlying buffer‘s length must then equal some multiple of the system’s page size.) Reading from and writing to the circular buffer may then be carried out with greater efficiency by means of direct memory access; those accesses which fall beyond the end of the first virtual-memory region will automatically wrap around to the beginning of the underlying buffer. When the read offset is advanced into the second virtual-memory region, both offsets—read and write—are decremented by the length of the underlying buffer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When implementing a circular buffer, we need to handle the case where a message spans the &amp;ldquo;discontinuity&amp;rdquo; in the queue and wraps around. The naive circular buffer&amp;rsquo;s write routine might employ a byte-by-byte write and look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The fact that a modulo operation is necessary to index into the array makes this function hard (if not impossible) to vectorize, and thus unnecessarily slow. Though there are other optimizations we can make, the technique offered in the above Wikipedia surpasses hardware-agnostic approaches by virtue of the fact that the memory management unit can handle most of the wrap-around logic on our behalf. I was so excited by this idea that I did no further research whatsoever, and implemented it based only on the brief description above.&lt;/p&gt;
&lt;p&gt;The next two sections overview the circular buffer&amp;rsquo;s design and the behavior of virtual memory, respectively. If you don&amp;rsquo;t need the refresher, feel free to skip ahead.&lt;/p&gt;
&lt;h2 id=&#34;the-circular-buffer&#34;&gt;The Circular Buffer&lt;/h2&gt;
&lt;p&gt;The circular buffer is a convenient approach to the storage of streaming data which is produced in real-time and consumed shortly thereafter. It &amp;ldquo;wraps around&amp;rdquo; so that new data may continually reuse the space previously occupied by data which has since been consumed.&lt;/p&gt;
&lt;p&gt;A circular buffer is typically implemented by storing two pointers (or indices): a &lt;em&gt;read pointer&lt;/em&gt; (I&amp;rsquo;ll call it &lt;code&gt;head&lt;/code&gt;) and a &lt;em&gt;write pointer&lt;/em&gt; (or &lt;code&gt;tail&lt;/code&gt;). As is obvious, we write into the buffer at &lt;code&gt;tail&lt;/code&gt;, and read from &lt;code&gt;head&lt;/code&gt;. The structure might be defined as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   head;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   tail;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   bytes_avail;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Given this, we can write simple read (&lt;code&gt;get&lt;/code&gt;) and write (&lt;code&gt;put&lt;/code&gt;) routines using byte-by-byte accesses:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data[i];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        data[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; i) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that this &lt;code&gt;put&lt;/code&gt; routine is identical to the one given above, with the exception that we now check that sufficient space is available before attempting to write. I also have purposefully neglected synchronization logic (which would be absolutely necessary for any real application of the circular buffer).&lt;/p&gt;
&lt;p&gt;The byte-by-byte access pattern and modular arithmetic embedded in each iteration of the loop make this code horrendously slow. We can instead implement each read or write operation with two &lt;code&gt;memcpy&lt;/code&gt; calls, where one handles the portion of a message at the end of the buffer, and the other handles the portion at the beginning if we wrapped around.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; a, &lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; b) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; a : b;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(size, q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; part1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail, data,         part1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer,           data &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; part1, part2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(size, q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; part1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(data,         q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head, part1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(data &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; part1, q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer,           part2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An example usage of this circular buffer would be as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; queue;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queue.buffer      &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;malloc&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;128&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queue.buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;128&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queue.head        &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queue.tail        &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    queue.bytes_avail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;q, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello &amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;q, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;world&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; s[&lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;q, (&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) s, &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(s); &lt;span style=&#34;color:#75715e&#34;&gt;// prints &amp;#34;hello world&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our code is simple, and it works fine. But why not make it more complicated?&lt;/p&gt;
&lt;h2 id=&#34;enter-the-page-table&#34;&gt;Enter the Page Table&lt;/h2&gt;
&lt;p&gt;In the early days of personal computing, computers could essentially only run one program at a time. Whatever program we ran would have full, direct access to the physical memory. It turns out that if we want to run several programs together, they have a tendency to fight over what regions of that memory they want to use. The solution to this conflict is &lt;em&gt;virtual memory&lt;/em&gt;, under which each program &lt;em&gt;thinks&lt;/em&gt; it controls &lt;em&gt;all&lt;/em&gt; of the memory, but in reality the operating system decides who&amp;rsquo;s getting memory from where.&lt;/p&gt;
&lt;p&gt;The key to virtual memory is the &lt;em&gt;page table&lt;/em&gt;, through which the operating system informs the CPU of mappings between &lt;em&gt;virtual pages&lt;/em&gt; and &lt;em&gt;physical pages&lt;/em&gt;. A page is a contiguous, aligned region of memory of a (typically) fixed size, say around 4 KiB. When a program wants to access some (virtual) memory, the CPU figures out what page it belongs to then uses the page table to index into the physical memory.&lt;/p&gt;
&lt;p&gt;The page table for some Program A might look something like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Virtual Page Number&lt;/th&gt;
&lt;th&gt;Physical Page Number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;em&gt;null&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;em&gt;null&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Meanwhile, Program B&amp;rsquo;s could be as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Virtual Page Number&lt;/th&gt;
&lt;th&gt;Physical Page Number&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;em&gt;null&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;em&gt;null&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The key point is that there doesn&amp;rsquo;t need to be any rhyme or reason to the way physical pages are assigned to virtual pages. They can be out-of-order with respect to the virtual pages, and some virtual pages don&amp;rsquo;t need physical memory assigned to them at all. Our two programs can use the same virtual page number to refer to different physical pages, and thus they don&amp;rsquo;t need to worry about conflicting with each other&amp;rsquo;s memory.&lt;/p&gt;
&lt;p&gt;The details behind figuring out the page numbers, assigning memory, and indexing into the page table are out of the scope of this article, but are unnecessary for understanding what&amp;rsquo;s to come.&lt;/p&gt;
&lt;h2 id=&#34;a-few-ordinary-system-calls-and-one-that-glibc-doesnt-want-you-to-know-about&#34;&gt;A Few Ordinary System Calls, and One That glibc Doesn&amp;rsquo;t Want You to Know About&lt;/h2&gt;
&lt;p&gt;Before we proceed to exploit the page table in optimizing our circular buffer, let&amp;rsquo;s review some common Linux system calls.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System Call&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;int getpagesize()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns the number of bytes in a page of memory. (Technically not always a system call, but close enough.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;int ftruncate(int fd, off_t length)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sets the size of a file to &lt;code&gt;length&lt;/code&gt; using its file descriptor, &lt;code&gt;fd&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maps the file described by &lt;code&gt;fd&lt;/code&gt; into memory and returns a pointer the new memory region. Can be manipulated to do evil through configuration &lt;code&gt;flags&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And now, a less common system call that glibc (our friendly userland interface to the kernel) doesn&amp;rsquo;t expose to us:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System Call&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;int memfd_create(const char *name, unsigned int flags)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returns a file descriptor for an &lt;code&gt;anonymous file&lt;/code&gt; which exists only in memory.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Interesting! Because glibc doesn&amp;rsquo;t implement it, we will need to write a little bit of wrapper code if we want to use it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;memfd_create&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;name, &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; flags) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;syscall&lt;/span&gt;(__NR_memfd_create, name, flags);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Excellent, now we&amp;rsquo;ll be able to call it like any other function. This is going to let us allocate memory and manipulate it like a file. Since it acts like a file, we&amp;rsquo;ll be able to mmap it wherever we want (spoiler alert).&lt;/p&gt;
&lt;h2 id=&#34;evil-black-magic-page-table-hacking&#34;&gt;Evil Black Magic Page Table Hacking&lt;/h2&gt;
&lt;p&gt;Okay, let&amp;rsquo;s get down to business. Let&amp;rsquo;s do what Wikipedia said and make two adjacent pages point to the same memory. We&amp;rsquo;ll need to modify our queue structure and extend its API with a convenient initialization method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;      fd;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   head;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt;   tail;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;init&lt;/span&gt;(queue &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// First, make sure the size is a multiple of the page size
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(size &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getpagesize&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Make an anonymous file and set its size
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;memfd_create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queue_buffer&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ftruncate&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fd, size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Ask mmap for an address at a location where we can put both virtual copies of the buffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mmap&lt;/span&gt;(NULL, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; size, PROT_NONE, MAP_PRIVATE &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; MAP_ANONYMOUS, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Map the buffer at that address
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mmap&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer, size, PROT_READ &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; PROT_WRITE, MAP_SHARED &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; MAP_FIXED, q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fd, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Now map it again, in the next virtual page
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mmap&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; size, size, PROT_READ &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; PROT_WRITE, MAP_SHARED &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; MAP_FIXED, q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;fd, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Initialize our buffer indices
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, what&amp;rsquo;s happening? Well, first we call &lt;code&gt;memfd_create&lt;/code&gt;, which does exactly what it says on the tin. We find some space for two copies of the buffer, and store that as our buffer address. Then we map the virtual file into memory, at the address mmap gave us. After that is where our evil kicks in.&lt;/p&gt;
&lt;p&gt;We ask &lt;code&gt;mmap&lt;/code&gt; to map our file again at the address &lt;code&gt;size&lt;/code&gt; bytes past its original location. Now, the page table is going to look something like this (assuming &lt;code&gt;size&lt;/code&gt; is larger than one page):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Virtual Address&lt;/th&gt;
&lt;th&gt;Physical Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q-&amp;gt;buffer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anonymous file &lt;code&gt;q-&amp;gt;fd&lt;/code&gt;, offset 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q-&amp;gt;buffer + getpagesize()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anonymous file &lt;code&gt;q-&amp;gt;fd&lt;/code&gt;, offset &lt;code&gt;getpagesize()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q-&amp;gt;buffer + size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anonymous file &lt;code&gt;q-&amp;gt;fd&lt;/code&gt;, offset 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q-&amp;gt;buffer + size + getpagesize()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anonymous file &lt;code&gt;q-&amp;gt;fd&lt;/code&gt;, offset &lt;code&gt;getpagesize()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Yes, we have two virtual pages that point to the same physical page, for each page in our buffer. Let&amp;rsquo;s see how this will affect our circular buffer logic:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;put&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail], data, size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;queue_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;q, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; size){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(data, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer[q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head], size);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;head &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;tail &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; q&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;buffer_size;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We allow our &lt;code&gt;tail&lt;/code&gt; offset to advance into the second virtual memory region, past the bounds of the &amp;ldquo;real&amp;rdquo; buffer. However, it still writes to the same physical memory. Similarly, we can read past the bounds of the first region, and by going into the second region we end up reading the same physical memory as if we had manually wrapped around.&lt;/p&gt;
&lt;p&gt;Notice that we need to make only one memcpy call instead of two. We check after reading data that the &lt;!-- raw HTML omitted --&gt;head&lt;!-- raw HTML omitted --&gt; pointer hasn&amp;rsquo;t moved into the second virtual copy of the buffer. If it did, we can decrement both by the buffer size; they will point to the same physical memory, though the virtual addresses will be different. The API remains the same as before.&lt;/p&gt;
&lt;h2 id=&#34;how-good-is-it&#34;&gt;How Good Is It?&lt;/h2&gt;
&lt;p&gt;The key difference here is that we can &lt;em&gt;always&lt;/em&gt; access contiguous blocks of virtual memory, instead of worrying about wrapping around mid-message. We can write a simple micro-benchmark that isolates this behavior as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;sys/time.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define BUFFER_SIZE (1024)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define MESSAGE_SIZE (32)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define NUMBER_RUNS (1000000)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;microtime&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; timeval tv;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;gettimeofday&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;tv, NULL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1e6&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; tv.tv_sec &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; tv.tv_usec;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; a, &lt;span style=&#34;color:#66d9ef&#34;&gt;off_t&lt;/span&gt; b) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; a : b;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; message[MESSAGE_SIZE];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; BUFFER_SIZE];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; offset;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; slow_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;microtime&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    offset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; NUMBER_RUNS; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;min&lt;/span&gt;(MESSAGE_SIZE, BUFFER_SIZE &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; offset);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; part2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MESSAGE_SIZE &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; part1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(buffer &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; offset, message, part1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(buffer, message &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; part1, part2);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        offset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (offset &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; MESSAGE_SIZE) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; BUFFER_SIZE;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; slow_stop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;microtime&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; fast_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;microtime&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    offset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; NUMBER_RUNS; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;buffer[offset], message, MESSAGE_SIZE);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        offset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (offset &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; MESSAGE_SIZE) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; BUFFER_SIZE;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;double&lt;/span&gt; fast_stop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;microtime&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;slow: %f microseconds per write&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, (slow_stop &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; slow_start) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; NUMBER_RUNS);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fast: %f microseconds per write&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, (fast_stop &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; fast_start) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; NUMBER_RUNS);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On my i5-6400, I get pretty consistent results, looking something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;slow: 0.012196 microseconds per write
fast: 0.004024 microseconds per write
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that the single-memcpy code is about three times faster than the double-memcpy code. We have a much larger margin over the naive byte-by-byte copy, which benchmarked at 0.104943 microseconds per write. This micro-benchmark is not wholly representative of the full queue logic (which could be affected by conditions such as page faults and TLB misses), however gives us a good indication that our optimization was worthwhile.&lt;/p&gt;
&lt;h2 id=&#34;what-just-happened&#34;&gt;What Just Happened?&lt;/h2&gt;
&lt;p&gt;Apparently this trick has been known for a while, but is seldom used. Nonetheless, it&amp;rsquo;s an excellent example of taking advantage of a machine&amp;rsquo;s low-level behaviors to optimize software.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in seeing my full circular buffer implementation, you can &lt;a href=&#34;https://github.com/tmick0/toy-queue/&#34; target=&#34;_blank&#34; &gt;find it on GitHub&lt;/a&gt;. The version there is complete with synchronization, error checking, and access at the granularity of arbitrarily-sized messages.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Proving a mathematical curiosity.</title>
      <link>https://lo.calho.st/posts/proving-a-mathematical-curiosity/</link>
      <pubDate>Wed, 21 Jun 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/proving-a-mathematical-curiosity/</guid>
      <description>&lt;p&gt;Today, a thread full of cool math facts appeared on Reddit. As usual, &lt;a href=&#34;https://www.reddit.com/r/AskReddit/comments/6il1jx/whats_the_coolest_mathematical_fact_you_know_of/dj720mc/&#34; target=&#34;_blank&#34; &gt;someone mentioned the fact that &lt;code&gt;111111111 * 111111111 = 12345678987654321&lt;/code&gt;&lt;/a&gt;. In another reply, someone pointed out that this also works in other bases. For some reason, I decided that I needed to prove that it works in &lt;em&gt;all&lt;/em&gt; bases.&lt;/p&gt;
&lt;p&gt;To begin, I needed a general formula for values of the 111&amp;hellip; terms. This was fairly straightforward: for a base $b$, we want $b-1$ base-$b$ digits, all ones. To standardize the base, we multiply each digit by an increasing power of $b$ and sum. Since each digit is one, we get a nice geometric series which can easily be solved.&lt;/p&gt;
&lt;p&gt;$$ \sum\limits_{i=1}^{b-1} b^{i-1} = \frac{b-b^b}{b-b^2}$$&lt;/p&gt;
&lt;p&gt;When we multiply this number by itself, we are squaring it, so we end up with $\left((b-b^b)/(b-b^2)\right)^2$.&lt;/p&gt;
&lt;p&gt;The hard part was writing a general form for the $1234 \dots (b-1) \dots 4321$ number. To deal with this, I broke it down into two parts, as illustrated below.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Digit value&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$(b-2)$&lt;/td&gt;
&lt;td&gt;$(b-1)$&lt;/td&gt;
&lt;td&gt;$(b-2)$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$b^{2b-3}$&lt;/td&gt;
&lt;td&gt;$b^{2b-4}$&lt;/td&gt;
&lt;td&gt;$\dots$&lt;/td&gt;
&lt;td&gt;$b^{2b-(b+1)}$&lt;/td&gt;
&lt;td&gt;$b^{2b-(b+2)}$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$\dots$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$b^{b-2}$&lt;/td&gt;
&lt;td&gt;$b^{b-3}$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$b^1$&lt;/td&gt;
&lt;td&gt;$b^0$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I calculated the values of the most-significant digits starting at the left, and the values of the least-significant digits starting at a right. To make the math come out nicely, I actually included the center digit in both formulas. That&amp;rsquo;s okay, since we can subtract it off once to make up for the duplicate. Now we have a summation formula for the value of the square.&lt;/p&gt;
&lt;p&gt;$$ \left( \sum\limits_{i=1}^{b-1} ib^{2b-(i+3)} \right) + \left( \sum\limits_{i=1}^{b-1} ib^{i-1} \right) - b^{b-2} $$&lt;/p&gt;
&lt;p&gt;With a little thinking (or the help of a computer algebra system), we can get a neat closed form.&lt;/p&gt;
&lt;p&gt;$$ \left( \sum\limits_{i=1}^{b-1} ib^{2b-(i+3)} + ib^{i-1} \right) - b^{b-2} = \left(\frac{b-b^b}{b^2-b}\right)^2 $$&lt;/p&gt;
&lt;p&gt;We can see that this is quite similar to the expression we got for the square above; the only difference is that the $b-b^2$ denominator has changed to $b^2-b$. Fortunately, this negation goes away when squaring, so we can trivially prove that the two expressions are equal.&lt;/p&gt;
&lt;p&gt;And there we have it: proof that this curiosity is true in any base of at least two.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Generating spectrograms the hard way with numpy.</title>
      <link>https://lo.calho.st/posts/numpy-spectrogram/</link>
      <pubDate>Thu, 18 May 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/numpy-spectrogram/</guid>
      <description>&lt;p&gt;A &lt;a href=&#34;https://en.wikipedia.org/wiki/Spectrogram&#34; target=&#34;_blank&#34; &gt;spectrogram&lt;/a&gt; is a convenient visualization of the frequencies present in an audio clip. Generating one involves obtaining the frequency components of each window of the audio via a &lt;a href=&#34;https://en.wikipedia.org/wiki/Discrete_Fourier_transform&#34; target=&#34;_blank&#34; &gt;Discrete Fourier Transform&lt;/a&gt; (DFT) of its waveform. While tools are available to both generate spectrograms and compute DFTs, I thought it would be fun to implement both myself in my language of choice, Python.&lt;/p&gt;
&lt;p&gt;In the following, I will discuss computing a DFT (the hard way), processing a WAV file, and rendering a spectrogram (all in Python). If you&amp;rsquo;re impatient and just want to see the code, you can &lt;a href=&#34;https://github.com/tmick0/spectrogram/blob/master/spectrogram.py&#34; target=&#34;_blank&#34; &gt;find it on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-discrete-fourier-transform&#34;&gt;The Discrete Fourier Transform.&lt;/h2&gt;
&lt;p&gt;A Fourier Transform converts a signal from the time domain (i.e., a waveform) to the frequency domain, wherein peaks represent dominant frequencies in the signal. The Discrete Fourier Transform (DFT) is the finite-resolution version of the Fourier Transform that we use for sampled signals such as audio clips.&lt;/p&gt;
&lt;p&gt;Usually, the DFT is computed using an algorithm called the &lt;a href=&#34;https://en.wikipedia.org/wiki/Fast_Fourier_transform&#34; target=&#34;_blank&#34; &gt;Fast Fourier Transform&lt;/a&gt; (FFT). However, the naive computation of the DFT just involves multiplying a vector of samples with what is known as the DFT matrix.&lt;/p&gt;
&lt;p&gt;The values of the DFT matrix are the same for each given size of sample vector. One may refer to the &lt;a href=&#34;https://en.wikipedia.org/wiki/DFT_matrix#Examples&#34; target=&#34;_blank&#34; &gt;relevant Wikipedia article&lt;/a&gt; for examples of the 2-element, 4-element, and 8-element DFT matrices. Each is computable through a general formula. To create a DFT matrix, simply assign each element of the matrix the value $\omega^{jk}$, where $j, k$ are the element indices. The $\omega$ itself is a constant determined by the vector size $N$: $\omega = e^{-2i\pi/N}$.&lt;/p&gt;
&lt;p&gt;It turns out that this type of matrix, where a constant is raised to a power depending on its index, is called a &lt;a href=&#34;https://en.wikipedia.org/wiki/Vandermonde_matrix&#34; target=&#34;_blank&#34; &gt;Vandermonde matrix&lt;/a&gt;. Fortunately, &lt;a href=&#34;https://docs.scipy.org/doc/numpy-1.12.0/reference/generated/numpy.vander.html&#34; target=&#34;_blank&#34; &gt;numpy gives us a useful method&lt;/a&gt; for computing such matrices. Therefore, in Python, we may simply compute the value of $\omega$:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;w &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exp(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;j &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pi &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; N)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then create a Vandermonde &amp;ldquo;matrix&amp;rdquo; which is really just a column vector:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;col &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vander([w], N, &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally build the complete Vandermonde matrix using that column:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;W &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vander(col&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;flatten(), N, &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To perform a DFT, we simply compute the dot product between this matrix $W$ and our vector $V$. Putting it all together, we have a very simple function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;matrix_dft&lt;/span&gt;(V):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    N &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; len(V)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    w &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exp(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;j &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pi &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; N)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    col &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vander([w], N, &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    W &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;vander(col&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;flatten(), N, &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sqrt(N)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dot(W, V)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that we scale by the term $1/\sqrt{N}$, as is conventional.&lt;/p&gt;
&lt;h2 id=&#34;catching-a-wav&#34;&gt;Catching a WAV.&lt;/h2&gt;
&lt;p&gt;WAV files are simple, and seemingly timeless. They&amp;rsquo;re the most simple format for storing uncompressed audio. Python ships with a &lt;a href=&#34;https://docs.python.org/2/library/wave.html&#34; target=&#34;_blank&#34; &gt;library for parsing them&lt;/a&gt;, so they will be quite convenient for the task at hand.&lt;/p&gt;
&lt;p&gt;First, we must open the file and determine important parameters such as the sample rate, number of channels, sample width, and number of frames.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;w &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; wave&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;open(input_file, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sample_rate  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getframerate()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;num_channels &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getnchannels()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sample_width &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getsampwidth()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;num_frames   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getnframes()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we ask to read a frame of audio, it will be given to us in raw form. Therefore, we need to know the sample width and number of channels in order to make sense of it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readframes(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromstring(x, dtype&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;int8 &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; sample_width &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;int16)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;reshape(x, (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, num_channels))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We begin by reading the raw frame. This gives us an array of bytes. We pass it to numpy&amp;rsquo;s &lt;a href=&#34;https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromstring.html&#34; target=&#34;_blank&#34; &gt;fromstring&lt;/a&gt; function, which will convert it to an array for us. Note that we must specify the datatype as int8 if the sample width is one byte (8 bits), or int16 if the sample width is two bytes (16 bits). Then, we reshape the array into a matrix wherein the channels are separated.&lt;/p&gt;
&lt;p&gt;This single audio sample isn&amp;rsquo;t incredibly useful, so we can also request a &lt;em&gt;window&lt;/em&gt; of some number of samples and process it the same way:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;window_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; w&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;readframes(window_size)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromstring(x, dtype&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;int8 &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; sample_width &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;int16)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;reshape(x, (window_size, num_channels))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will be quite necessary shortly, when we begin computing DFTs in order to generate our spectrogram.&lt;/p&gt;
&lt;h2 id=&#34;transforming-the-window&#34;&gt;Transforming the Window.&lt;/h2&gt;
&lt;p&gt;Once we have our window of audio, we can do some magic to it to get spectrum data out. If the audio is stereo, we will first mix down to mono before we run a DFT. Then, we must perform some postprocessing to make that output useful.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; num_channels &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (x[:,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; x[:,&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; x[:,&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fft&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;rfft(x)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; y[:window_size&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;absolute(y) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; window_size
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; y &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;power(&lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;sample_width &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;log10(y))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clip(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To mix together the two channels, we simple take their average. Note that instead of using the DFT function we implemented earlier, we simply called the &lt;a href=&#34;https://docs.scipy.org/doc/numpy/reference/routines.fft.html&#34; target=&#34;_blank&#34; &gt;FFT provided by numpy&lt;/a&gt; because it is &lt;em&gt;way faster&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You may be surprised to see that we have discarded half of the output. This is because the DFT result is symmetric, and thus the second half is redundant and we won&amp;rsquo;t need it. You may have noticed earlier that the DFT matrix itself is symmetric across its diagonal &amp;ndash; that explains this result.&lt;/p&gt;
&lt;p&gt;Next, we take the absolute magnitude of the DFT, which gives us a real-valued magnitude. We scale by a factor of 2 because we originally discarded half of the spectrum, then divide by the window size to give us a value of magnitude per sample. We divide again by the value $2^{8s-1}$, where $s$ is the sample width, to give us an intensity relative to the maximum intensity possible with a sample of that size (recall that $s$ bytes equals $8s$ bits, and samples are signed integers). Finally, we compute $20log_{10}(y)$ to convert to decibels (dB) and filter out values below -120 dB.&lt;/p&gt;
&lt;p&gt;Now, we have successfully computed the spectral content of a single window of audio. We may perform the above computation in a loop in order to generate spectra for each frame. In my implementation, I appended each of the above results &lt;code&gt;y&lt;/code&gt; to a list &lt;code&gt;Y&lt;/code&gt;. By combining all of these frames, we can produce a spectrogram.&lt;/p&gt;
&lt;h2 id=&#34;plotting-the-spectrum&#34;&gt;Plotting the Spectrum.&lt;/h2&gt;
&lt;p&gt;I chose to use &lt;a href=&#34;https://matplotlib.org/&#34; target=&#34;_blank&#34; &gt;matplotlib&lt;/a&gt; to visualize my data, since it is the plotting system most familiar to me.&lt;/p&gt;
&lt;p&gt;It turns out, the first thing we must do is transpose the list &lt;code&gt;Y&lt;/code&gt;. By default, numpy treats each subsequent input &lt;code&gt;y&lt;/code&gt; as a row, rather than a column (as we want for the purpose of a spectrogram).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array(Y)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;transpose()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For quick-and-dirty validation purposes, we can visualize this using the &lt;a href=&#34;https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow&#34; target=&#34;_blank&#34; &gt;imshow&lt;/a&gt; function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;imshow(Y)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;show()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result for a 440-Hz sine wave looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-13-16.png&#34; alt=&#34;spectrogram of 440 hz sine wave&#34;&gt;&lt;/p&gt;
&lt;p&gt;It looks pretty good, but there are a couple problems. First, the frequencies are plotted upside down. Then, notice that our axis labels are simply pixel coordinates. While there are hacks to fix both of these, it will be much easier to use the &lt;a href=&#34;https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.pcolormesh&#34; target=&#34;_blank&#34; &gt;pcolormesh&lt;/a&gt; function instead. However, this requires us to first generate arrays for our time and frequency coordinates, which we will use as our x and y coordinates when plotting.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;arange(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, len(Y), dtype&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;float) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; window_size &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; sample_rate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;f &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;arange(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, window_size &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, dtype&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;float) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; sample_rate &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; window_size
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the time coordinates, we start with an array of indices of each of our window spectra in &lt;code&gt;Y&lt;/code&gt;. Then, we multiply by the window size (number of samples per window) and divide by the sample rate (number of samples per second). The result is the time coordinate, in seconds, for each window.&lt;/p&gt;
&lt;p&gt;The frequency coordinates are generated similarly. We start with the indices of our &lt;code&gt;window\_size / 2&lt;/code&gt; frequency values, then multiply by the sample rate and divide by the window size. This is because the spacing of frequencies returned by the DFT is given by $f/N$, where $f$ is the sample rate and $N$ is the window size.&lt;/p&gt;
&lt;p&gt;Having generated our coordinate arrays, we can finally call pcolormesh. Note that we specify a range of -120 dB to 0 dB for the colormap.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pcolormesh(t, f, Y, vmin&lt;span style=&#34;color:#f92672&#34;&gt;=-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt;, vmax&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result is still not that pretty, however at least now everything is right-side up and our axes are numbered appropriately.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-14-06.png&#34; alt=&#34;spectrogram plotted with pcolormesh&#34;&gt;&lt;/p&gt;
&lt;p&gt;To complete the presentation of our spectrogram, we only have a few more changes to make. We will use a &amp;lsquo;symlog&amp;rsquo; scale (which is logarithmic above a certain threshold, and linear below it), set our axes limits, label the axes, and display a colorbar.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;yscale(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;symlog&amp;#39;&lt;/span&gt;, linthreshy&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;, linscaley&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0.25&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ax&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;yaxis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_major_formatter(matplotlib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ticker&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ScalarFormatter())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xlim(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, t[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ylim(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, f[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xlabel(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Time (s)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ylabel(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Frequency (Hz)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cbar &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; plt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;colorbar()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cbar&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_label(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Intensity (dB)&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result is now quite acceptable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-15-42.png&#34; alt=&#34;spectrogram plotted with log scale&#34;&gt;&lt;/p&gt;
&lt;p&gt;We can even run an analysis on &lt;a href=&#34;https://soundcloud.com/eclipsical/eclipsical-x-mud-exdr&#34; target=&#34;_blank&#34; &gt;an entire song&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-16-33.png&#34; alt=&#34;spectrogram of a song&#34;&gt;&lt;/p&gt;
&lt;p&gt;There we have it, a fully-functional spectrogram tool.&lt;/p&gt;
&lt;h2 id=&#34;completing-the-package&#34;&gt;Completing the Package.&lt;/h2&gt;
&lt;p&gt;To make things a bit more useful, I added a progress bar while the DFTs are being calculated, and added an option to change the window size at runtime. With larger windows, you will notice much better resolution in the output.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-17-17.png&#34; alt=&#34;spectrogram computed with a larger window&#34;&gt;&lt;/p&gt;
&lt;p&gt;As previously mentioned, the &lt;a href=&#34;https://github.com/tmick0/spectrogram/blob/master/spectrogram.py&#34; target=&#34;_blank&#34; &gt;complete source is available in GitHub&lt;/a&gt;. While it isn&amp;rsquo;t incredibly useful for real applications, this was quite a fun project, especially considering that I had no prior background in practical signals analysis. For real-world applications, you might want to consider &lt;a href=&#34;https://matplotlib.org/examples/pylab_examples/specgram_demo.html&#34; target=&#34;_blank&#34; &gt;matplotlib&amp;rsquo;s builtin spectrogram&lt;/a&gt; capabilities, or a standalone tool such as &lt;a href=&#34;http://spek.cc/&#34; target=&#34;_blank&#34; &gt;Spek&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;slug: numpy-spectrogram&lt;/p&gt;
&lt;h2 id=&#34;update-window-function&#34;&gt;Update: Window Function.&lt;/h2&gt;
&lt;p&gt;I was &lt;a href=&#34;https://github.com/tmick0/spectrogram/issues/1&#34; target=&#34;_blank&#34; &gt;given a suggestion&lt;/a&gt; to implement a smooth (rather than rectangular) window function, and after reading &lt;a href=&#34;http://www.katjaas.nl/FFTwindow/FFTwindow.html&#34; target=&#34;_blank&#34; &gt;this guide&lt;/a&gt; I found that it was quite straightforward. The calculation of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Hann_function&#34; target=&#34;_blank&#34; &gt;Hann window function&lt;/a&gt; was a one-liner:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hann &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cos(&lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pi &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; (np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;arange(window_size)) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; window_size)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I then multiplied these coefficients against the audio window before feeding it to the FFT. Because the window is tapered, I also had to implement a running window with 4x overlap in order to cover the full signal. The result has significantly less spectral leakage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;Screenshot-from-2017-05-19-13-18-48.png&#34; alt=&#34;comparison of spectrograms using rectangular and Hann windows&#34;&gt;&lt;/p&gt;
&lt;p&gt;The plot on the left has a rectangular window, and the plot on the right has the Hann window. It is visibly more crisp. As always, the &lt;a href=&#34;https://github.com/tmick0/spectrogram/commit/86a19447877c5f980996b01b7d839a60539fc592&#34; target=&#34;_blank&#34; &gt;full updated code is on GitHub&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Integrating GitLab and Google Calendar.</title>
      <link>https://lo.calho.st/posts/gitlab-calendar/</link>
      <pubDate>Tue, 25 Apr 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/gitlab-calendar/</guid>
      <description>&lt;p&gt;Zeall, like many other software startups, uses &lt;a href=&#34;https://about.gitlab.com/&#34; target=&#34;_blank&#34; &gt;GitLab&lt;/a&gt; for version control and issue management. We also use the ever-popular &lt;a href=&#34;https://calendar.google.com/&#34; target=&#34;_blank&#34; &gt;Google Calendar&lt;/a&gt; to handle meetings, reminders, and deadlines. For several months, we&amp;rsquo;ve been looking for a way to automatically push GitLab issue deadlines into Google Calendar, and until now it seemed impossible. Only after a recent migration from our own private mailserver to &lt;a href=&#34;https://gsuite.google.com/&#34; target=&#34;_blank&#34; &gt;G Suite&lt;/a&gt; did we find a solution &amp;ndash; or rather, figure out how to feasibly build one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;gitlab-calendar-screenshot.png&#34; alt=&#34;visualization of a gitlab issue and corresponding calendar entry&#34;&gt;&lt;/p&gt;
&lt;p&gt;GitLab&amp;rsquo;s webhooks make it pretty easy to obtain information about new and modified issue deadlines, however the problem has always been pushing that information into Google Calendar. I had previously explored the &lt;a href=&#34;https://developers.google.com/google-apps/calendar/&#34; target=&#34;_blank&#34; &gt;Google Calendar API&lt;/a&gt; and ultimately decided that implementing our own solution wasn&amp;rsquo;t worth it, simply because authorization would be a nightmare &amp;ndash; there was no easy way to share a calendar with our team&amp;rsquo;s members. However, once we got a G Suite domain, I found it was possible to use a domain scope for &lt;a href=&#34;https://developers.google.com/google-apps/calendar/v3/reference/acl&#34; target=&#34;_blank&#34; &gt;Calendar ACLs&lt;/a&gt;. I simply created a &lt;a href=&#34;https://developers.google.com/identity/protocols/OAuth2ServiceAccount&#34; target=&#34;_blank&#34; &gt;service account&lt;/a&gt; for our integration, set it up to grant read access to our entire domain, and implemented some basic logic to map GitLab issues to calendar events.&lt;/p&gt;
&lt;p&gt;As far as I can tell, I&amp;rsquo;m the first person to have successfully integrated GitLab issues into Google Calendar; however, I&amp;rsquo;m sure I&amp;rsquo;m not the first to &lt;em&gt;want to&lt;/em&gt; do so. Therefore, to spare others from sharing in my misery I&amp;rsquo;ve decided to make the fruit of my labor &lt;a href=&#34;https://github.com/tmick0/gitlab-calendar&#34; target=&#34;_blank&#34; &gt;available on GitHub&lt;/a&gt; for anybody to use. Try it out, and be sure to let me know if it breaks.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Adding custom fields to packets in ndnSIM 2.3 without forking the entire repository.</title>
      <link>https://lo.calho.st/posts/ndnsim-custom-fields/</link>
      <pubDate>Thu, 16 Mar 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/ndnsim-custom-fields/</guid>
      <description>&lt;p&gt;The recommended way to build something on top of ndnSIM is to fork its &lt;a href=&#34;https://github.com/cawka/ndnSIM-scenario-template&#34; target=&#34;_blank&#34; &gt;scenario template&lt;/a&gt; repository and work inside there. You still need to &lt;a href=&#34;http://ndnsim.net/2.3/getting-started.html&#34; target=&#34;_blank&#34; &gt;download and compile&lt;/a&gt; the actual framework, however you will simply install it into &lt;code&gt;/usr/local&lt;/code&gt; and link to it instead of actually working inside the main repository.&lt;/p&gt;
&lt;p&gt;It turns out that this workflow actually makes certain tasks a lot more difficult. You might think a network simulator would make it easy to add new header fields to packets. Well, think again.&lt;/p&gt;
&lt;h2 id=&#34;first-steps&#34;&gt;First Steps&lt;/h2&gt;
&lt;p&gt;What do we want to do? Our goal is to just add one field to the Interest packet header. The &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/classndn_1_1Interest.html&#34; target=&#34;_blank&#34; &gt;ndn::Interest&lt;/a&gt; class inherits an interface called &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/classndn_1_1TagHost.html&#34; target=&#34;_blank&#34; &gt;ndn::TagHost&lt;/a&gt;, which allows you to attach arbitrary tags to it. Defining your own tag can be as simple as a single typedef, if you only need to contain a single value in that tag:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; ndn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;SimpleTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x60000001&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; MyCustomTag;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You simply specify the type of the tag and make up an ID for it. However, you must &lt;a href=&#34;https://redmine.named-data.net/projects/ndn-cxx/wiki/PacketTagTypes&#34; target=&#34;_blank&#34; &gt;pick an unused tag from the valid range&lt;/a&gt; given in the ndn-cxx wiki. My &lt;code&gt;0x60000001&lt;/code&gt; is the first value in this range.&lt;/p&gt;
&lt;p&gt;To attach a tag to an Interest, you simply call the &lt;code&gt;setTag&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;interest.setTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;make_shared&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;54321&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To read a tag from an Interest, there is a corresponding &lt;code&gt;getTag&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;shared_ptr&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; tag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interest.getTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives you a pointer to the tag object, and you can get the value out of it quite easily&amp;hellip; But first, check if it is null.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (tag &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nullptr&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// no tag
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; tagValue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tag&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;get();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, now is where we encounter our problem. Our tag &lt;em&gt;will not actually be encoded and sent over the network&lt;/em&gt;. That&amp;rsquo;s right &amp;ndash; we can attach a tag to the Interest, but when it arrives at the next hop &lt;em&gt;it will be gone&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;How can we fix this?&lt;/p&gt;
&lt;h2 id=&#34;investigation&#34;&gt;Investigation&lt;/h2&gt;
&lt;p&gt;Vanilla ndnSIM uses these sorts of tags itself in a few places. One obvious one is the HopCountTag, which you can use to figure out how far a packet has gone in the network. A grep through the ndnSIM source brings us to a class called &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/classnfd_1_1face_1_1GenericLinkService.html&#34; target=&#34;_blank&#34; &gt;GenericLinkService&lt;/a&gt;. This class is responsible for actually encoding packets and sending them out on the wire. In particular, we can find the bit responsible for encoding the HopCountTag in a method called &lt;code&gt;encodeLpFields&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shared_ptr&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HopCountTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; hopCountTag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; netPkt.getTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HopCountTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (hopCountTag &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nullptr&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lpPacket.add&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HopCountTagField&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;hopCountTag);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lpPacket.add&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HopCountTagField&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Clearly, we need to define a MyCustomTagField to be able to encode our new tag.&lt;/p&gt;
&lt;h2 id=&#34;declaring-a-tag&#34;&gt;Declaring a Tag&lt;/h2&gt;
&lt;p&gt;This is actually pretty easy, but first you need to know what kind of witchcraft is going on. Let&amp;rsquo;s start with the actual code to define the field, then go on to analyze it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; TlvMyCustomTag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;901&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; ndn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;detail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FieldDecl&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;ndn&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;lp&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;field_location_tags&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;Header, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt;, TlvMyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; MyCustomTagField;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, we define a constant for the TLV type ID&amp;hellip; There are actually a few hidden constraints to what we can pick. If we don&amp;rsquo;t do this right, we get a packet parse error. Why?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at &lt;code&gt;ndn::lp::Packet&lt;/code&gt;&amp;rsquo;s &lt;code&gt;wireDecode&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; Block&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; element : wire.elements()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    detail&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FieldInfo info(element.type());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;info.isRecognized &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;info.canIgnore) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        BOOST_THROW_EXCEPTION(Error(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unrecognized field cannot be ignored&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Apparently, this &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/classndn_1_1lp_1_1detail_1_1FieldInfo.html&#34; target=&#34;_blank&#34; &gt;FieldInfo&lt;/a&gt; class tells the decoder whether the field is recognized, and whether it can ignore it if it isn&amp;rsquo;t. Let&amp;rsquo;s peek at the constructor:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FieldInfo&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;FieldInfo(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint64_t&lt;/span&gt; tlv)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   boost&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;mpl&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;for_each&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;FieldSet&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(boost&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;bind(ExtractFieldInfo(), &lt;span style=&#34;color:#66d9ef&#34;&gt;this&lt;/span&gt;, _1));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;isRecognized) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     canIgnore &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tlv&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HEADER3_MIN &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; tlvType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tlvType &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; tlv&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;HEADER3_MAX
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (tlvType &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now this is interesting&amp;hellip; To figure out what a TLV tag is, it iterates over FieldSet (which only contains the built-in tags, and we can&amp;rsquo;t override). However, if it doesn&amp;rsquo;t find a match, it determines if it is ignorable based on the value of the TLV type ID. We can&amp;rsquo;t make the field recognized without forking the actual ndnSIM core, but we can make it ignorable by choosing the right ID.&lt;/p&gt;
&lt;p&gt;To save you from looking up &lt;code&gt;tlv::HEADER3_MIN&lt;/code&gt; and &lt;code&gt;tlv::HEADER3_MAX&lt;/code&gt;, they are 800 and 959, respectively. Also, don&amp;rsquo;t forget that the low bit has to be set. And don&amp;rsquo;t pick &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/lp_2tlv_8hpp_source.html&#34; target=&#34;_blank&#34; &gt;one of the types that is already used&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Moving on from the TLV ID nonsense, the rest of the FieldDecl is pretty straightforward. We pass a flag that says &amp;ldquo;this goes in the header,&amp;rdquo; followed by the type of the value and the TLV ID we just made up.&lt;/p&gt;
&lt;p&gt;Note that for some reason, the code won&amp;rsquo;t compile if the type is specified as anything other than &lt;code&gt;uint64_t&lt;/code&gt;. I didn&amp;rsquo;t care enough to figure this out, but it seems to have something to do with the fact that &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/field-decl_8hpp_source.html#l00085&#34; target=&#34;_blank&#34; &gt;the only integer EncodeHelper defined is for &lt;code&gt;uint64_t&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;encoding-the-tag&#34;&gt;Encoding the Tag&lt;/h2&gt;
&lt;p&gt;So far, we have defined our tag twice: once for the high-level Interest object, and once for the low-level TLV encoding. Now, we need to write code to convert between these two representations.&lt;/p&gt;
&lt;p&gt;To do this, we need to create a new LinkService. Sounds intimidating, but really all we need to do is make a copy of GenericLinkService and change a few things. Yes, literally copy &lt;code&gt;generic-link-service.hpp&lt;/code&gt; and &lt;code&gt;generic-link-service.cpp&lt;/code&gt; out of &lt;code&gt;ns3/ndnSIM/NFD/daemon/face/&lt;/code&gt; and into your own project. Rename the file as you see fit, and carefully rename the class to something like CustomTagLinkService. You will want to be careful because we still need to implement the GenericLinkServiceCounters interface if we don&amp;rsquo;t want to break anything. We can also avoid redefining the nested Options class by using a typedef to import it from GenericLinkService into the new CustomTagLinkService namespace.&lt;/p&gt;
&lt;p&gt;Now that we have an identical clone of the GenericLinkService, let&amp;rsquo;s fix it. To encode your new field, take a look at the &lt;code&gt;encodeLpFields&lt;/code&gt; method. Follow the pattern used by the CongestionMarkTag field to implement your new custom one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shared_ptr&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; myCustomTag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; netPkt.getTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (myCustomTag &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nullptr&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lpPacket.add&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTagField&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;myCustomTag);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, add the corresponding decoding logic to &lt;code&gt;decodeInterest&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (firstPkt.has&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTagField&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;()) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    interest&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;setTag(make_shared&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTag&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;(firstPkt.get&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MyCustomTagField&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;()));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add the same code to the decodeData and decodeNack methods if you need them.&lt;/p&gt;
&lt;h2 id=&#34;using-the-linkservice&#34;&gt;Using the LinkService&lt;/h2&gt;
&lt;p&gt;Specifying a custom LinkService isn&amp;rsquo;t going to do us any good if we don&amp;rsquo;t tell ndnSIM to use it. We&amp;rsquo;ll have to replace the callback that sets up a Face in order to do this. We&amp;rsquo;re going to focus on Faces for PointToPointNetDevices, but the following can be generalized for other types of links.&lt;/p&gt;
&lt;p&gt;The call from our scenario file will look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stackHelper.UpdateFaceCreateCallback(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PointToPointNetDevice&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;GetTypeId(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MakeCallback(CustomTagNetDeviceCallback)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For context, this is a method of the &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/classns3_1_1ndn_1_1StackHelper.html&#34; target=&#34;_blank&#34; &gt;StackHelper&lt;/a&gt; that you&amp;rsquo;re probably already using to install the NDN stack on nodes. To write the callback, copy the logic from the &lt;a href=&#34;http://ndnsim.net/2.3/doxygen/ndn-stack-helper_8cpp_source.html#l00295&#34; target=&#34;_blank&#34; &gt;PointToPointNetDeviceCallback&lt;/a&gt; in that same class. All you have to change is the instantiation of the LinkService &amp;ndash; replace the GenericLinkService with your own. You will also need to copy the constructFaceUri method (verbatim) because your callback will need to refer to it, but it is out of scope.&lt;/p&gt;
&lt;h2 id=&#34;other-caveats&#34;&gt;Other Caveats&lt;/h2&gt;
&lt;p&gt;By default, the scenario template wants to compile your code in C++11 mode. However, the LinkService uses some C++14 features, so you&amp;rsquo;ll have to edit the flags in &lt;code&gt;.waf-tools/default-compiler-flags.py&lt;/code&gt;. Note that you need to re-run &lt;code&gt;./waf configure&lt;/code&gt; if you edit these flags.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think this is way too much effort just to add a field to a packet. We&amp;rsquo;ve duplicated a lot of logic in order to do something so small. I feel like the ndnSIM developers should have made it a bit easier to add fields to a packet&amp;hellip; At worst, I might expect a call to the StackHelper to add new fields. It would likely be possible to write a generic enough LinkService which will encode any custom fields as long as mappings between the TLV classes and tag classes are provided. I look forward to this feature, because it would have made the middle part of my week go a lot more smoothly. Until then, I hope that this post can be useful to anyone else trying to do the same thing.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>An idiot&#39;s guide to fulltext search in PostgreSQL.</title>
      <link>https://lo.calho.st/posts/postgresql-fulltext-search/</link>
      <pubDate>Thu, 23 Feb 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/postgresql-fulltext-search/</guid>
      <description>&lt;p&gt;I love PostgreSQL. It&amp;rsquo;s probably the most powerful open-source database system out there. Recent features to handle &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/datatype-json.html&#34; target=&#34;_blank&#34; &gt;JSON&lt;/a&gt; and &lt;a href=&#34;http://www.postgis.net/&#34; target=&#34;_blank&#34; &gt;geospatial&lt;/a&gt; data are allowing it to supplant specialized database systems and become closer to a one-DB-fits-all solution. One feature that I&amp;rsquo;ve recently been able to exploit is its &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch.html&#34; target=&#34;_blank&#34; &gt;fulltext search&lt;/a&gt; engine. It allowed me to easily move from a terrible search implementation (using regular expressions) to one that actually meets users&amp;rsquo; expectations.&lt;/p&gt;
&lt;p&gt;In this article, I will walk through a basic fulltext search configuration, as well as highlight a few potential improvements that can be made if you&amp;rsquo;re so inclined.&lt;/p&gt;
&lt;p&gt;Many of the features discussed in this post are only available as of PostgreSQL 9.6. Earlier versions have some rudimentary fulltext functionality, but a lot of the more powerful tools we&amp;rsquo;ll be using are fairly new.&lt;/p&gt;
&lt;h2 id=&#34;the-wrong-way-to-search&#34;&gt;The Wrong Way To Search&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with a basic table:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;create&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;table&lt;/span&gt; test_table (id serial, title text, description text);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And populate it with some bogus data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;insert&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;into&lt;/span&gt; test_table (title, description) &lt;span style=&#34;color:#66d9ef&#34;&gt;values&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Hello world!&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;This is an example.&amp;#39;&lt;/span&gt;), (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Filler text&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo bar baz&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How might we go about doing a simple search on this table? The naive strategy, which I originally might have used, would go something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; title &lt;span style=&#34;color:#f92672&#34;&gt;~*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;or&lt;/span&gt; description &lt;span style=&#34;color:#f92672&#34;&gt;~*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the result (which is correct, in this case):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;id  | title        | description
----+--------------+---------------------
1   | Hello world! | This is an example.
(1 row)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, consider that a human searching for something might not always know &lt;em&gt;exactly&lt;/em&gt; what they&amp;rsquo;re looking for. Let&amp;rsquo;s put a new row in that table:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;insert&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;into&lt;/span&gt; test_table (title, description) &lt;span style=&#34;color:#66d9ef&#34;&gt;values&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;A helpful example&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;This is where things get complicated&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s say a user &lt;em&gt;kind of&lt;/em&gt; knows what they&amp;rsquo;re looking for: they enter the query &amp;ldquo;gets complicated&amp;rdquo;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; description &lt;span style=&#34;color:#f92672&#34;&gt;~*&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;gets complicated&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is no result. Why? Because we have no way to identify that &amp;ldquo;gets&amp;rdquo; and &amp;ldquo;get&amp;rdquo; are semantically equivalent.&lt;/p&gt;
&lt;p&gt;Also consider that a regex-based search is &lt;em&gt;at least&lt;/em&gt; linear in the length of each string we check (unless you enable trigram indices, apparently, but that&amp;rsquo;s out of scope of this article). If you&amp;rsquo;re searching a large table, you&amp;rsquo;re going to be in big trouble.&lt;/p&gt;
&lt;p&gt;Fulltext search solves both of these problems: It treats semantically equivalent words as matches, and can be drastically sped up by virtue of indices.&lt;/p&gt;
&lt;h2 id=&#34;a-better-solution&#34;&gt;A Better Solution&lt;/h2&gt;
&lt;p&gt;To handle fulltext searches, there are two main things we have to do: add a &lt;code&gt;tsvector&lt;/code&gt; to the table to store keywords, and write a &lt;code&gt;tsquery&lt;/code&gt; when we want to perform a search.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;tsvector&lt;/code&gt; is essentially a list of keywords. We can assign one to each row in the table. Postgres will automatically filter out common words and replace similar words with roots or synonyms. Here&amp;rsquo;s an example query, to demonstrate how a human-written string is translated into an indexable &lt;code&gt;tsvector&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; to_tsvector(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;This is where things get complicated&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;to_tsvector
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;complic&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;get&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;thing&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;row&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can see that the input got filtered down into three keywords. One of them, &amp;ldquo;complicated,&amp;rdquo; got trimmed down to the root &amp;ldquo;complic.&amp;rdquo; The rules for how this translation takes place are defined by the &lt;code&gt;regconfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Your default &lt;code&gt;regconfig&lt;/code&gt; is probably either &amp;ldquo;english&amp;rdquo; or &amp;ldquo;simple.&amp;rdquo; It can be retrieved by selecting &lt;code&gt;get_current_ts_config()&lt;/code&gt;, or set with the &lt;code&gt;default_text_search_config&lt;/code&gt; directive. If you need to change it per-query, you can stick it in any call to &lt;code&gt;to_tsvector&lt;/code&gt; or its related functions, e.g. &lt;code&gt;to_tsvector(&#39;english&#39;, &#39;hello world&#39;)&lt;/code&gt;. In most cases, you&amp;rsquo;ll want &amp;ldquo;english&amp;rdquo; (or some other human language) rather than &amp;ldquo;simple&amp;rdquo; (which doesn&amp;rsquo;t handle magic such as removing meaningless words and distilling words into lexemes). Read &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-configuration.html&#34; target=&#34;_blank&#34; &gt;the documentation&lt;/a&gt; if you think you&amp;rsquo;ll need to do anything funky with regconfig.&lt;/p&gt;
&lt;p&gt;Now that we understand what we&amp;rsquo;re doing, let&amp;rsquo;s add a tsvector column to the table:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;alter&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;table&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;add&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;column&lt;/span&gt; tsv tsvector;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: It isn&amp;rsquo;t always necessary to create a dedicated &lt;code&gt;tsvector&lt;/code&gt; column (i.e., you can skip right ahead to creating a fulltext index on a concatenation of existing columns), but I&amp;rsquo;ve done it here because it&amp;rsquo;s a more flexible option and it&amp;rsquo;s easier to reason about.&lt;/p&gt;
&lt;p&gt;How do we populate this column? Well, it&amp;rsquo;s quite easy to add a trigger that does it for us. We just tell it the name of our &lt;code&gt;tsvector&lt;/code&gt; column, the language to use for processing, and then list all of the fields that should be considered for keywords:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;create&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;trigger&lt;/span&gt; test_tsv &lt;span style=&#34;color:#66d9ef&#34;&gt;before&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;insert&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;update&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;on&lt;/span&gt; test_table
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;each&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;row&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;execute&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;procedure&lt;/span&gt; tsvector_update_trigger(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tsv, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pg_catalog.english&amp;#39;&lt;/span&gt;, title, description
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that &lt;code&gt;&#39;pg_catalog.english&#39;&lt;/code&gt; is actually our &lt;code&gt;regconfig&lt;/code&gt; showing up again. It is mandatory to supply a &lt;code&gt;regconfig&lt;/code&gt; to &lt;code&gt;tsvector_update_trigger&lt;/code&gt;, and you must fully qualify it with the schema name (&lt;code&gt;pg_catalog&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Now, any new or updated tuples will be given a &lt;code&gt;tsvector&lt;/code&gt; containing the title and description. Let&amp;rsquo;s see it in action:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;insert&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;into&lt;/span&gt; test_table (title, description) &lt;span style=&#34;color:#66d9ef&#34;&gt;values&lt;/span&gt; (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;We have tsvectors now!&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Pretty cool, eh?&amp;#39;&lt;/span&gt;) returning tsv;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tsv
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-----------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cool&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;eh&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pretti&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;tsvector&amp;#39;&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;row&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can do a quick hack to trigger the hook and create vectors for our pre-existing rows. Be careful doing this in production.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;update&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;id;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Creating the tsvectors was half the battle. Now, how do we query them? The quite intuitive answer: &lt;code&gt;tsquery&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;tsquery&lt;/code&gt;, like a &lt;code&gt;tsvector&lt;/code&gt;, is essentially a list of keywords. However, we can do some fun things, like apply boolean operations and specify that words have to be in a certain order. Here&amp;rsquo;s a list of a few useful operators we can abuse:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;foo &amp;lt;-&amp;gt; bar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches &amp;ldquo;foo&amp;rdquo; followed by &amp;ldquo;bar&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;foo &amp;lt;2&amp;gt; baz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches &amp;ldquo;foo&amp;rdquo; with &amp;ldquo;baz&amp;rdquo; two words later (i.e., one word in between). Replace the 2 with another number to match a larger &amp;ldquo;gap.&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;foo &amp;amp; bar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches a vector that contains both &amp;ldquo;foo&amp;rdquo; and &amp;ldquo;bar,&amp;rdquo; in any order and with anything in between.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`foo&lt;/td&gt;
&lt;td&gt;bar`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!baz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches a vector that doesn&amp;rsquo;t contain &amp;ldquo;baz.&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here&amp;rsquo;s an example of a simple tsquery, to see how it is interpreted:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; to_tsquery(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo &amp;lt;-&amp;gt; baz&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;to_tsquery
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-----------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s how we would use it to search (say hello to the &lt;code&gt;@@&lt;/code&gt; operator):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; id, title, description &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; tsv &lt;span style=&#34;color:#f92672&#34;&gt;@@&lt;/span&gt; to_tsquery(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo &amp;lt;-&amp;gt; bar&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;id  &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; title       &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; description 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;----+-------------+-------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;   &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; Filler text &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; foo bar baz 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of course, we&amp;rsquo;re not going to get our users to type their queries in this format. Fortunately, Postgres gives us a few helper functions for creating queries. Let&amp;rsquo;s take a look at both &lt;code&gt;plainto_tsquery&lt;/code&gt; and &lt;code&gt;phraseto_tsquery&lt;/code&gt;, and see how they differ:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; plainto_tsquery(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo baz&amp;#39;&lt;/span&gt;), phraseto_tsquery(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo baz&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; plainto_tsquery &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; phraseto_tsquery 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-----------------+------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;amp; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;   &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;row&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most noticeably, &lt;code&gt;plainto_tsquery&lt;/code&gt; just matches for the boolean conjunction of all words in the input, while &lt;code&gt;phraseto_tsquery&lt;/code&gt; matches the exact ordering. Choose the one most appropriate for your application. You can easily make queries using a condition like &lt;code&gt;where tsv @@ plainto_tsquery(user_input_here)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point, you&amp;rsquo;re able to handle simple fulltext queries. Congratulations. If you&amp;rsquo;re interested, read on for some interesting tweaks and optimizations.&lt;/p&gt;
&lt;h2 id=&#34;even-better&#34;&gt;Even Better&lt;/h2&gt;
&lt;p&gt;The next thing you&amp;rsquo;re going to want to add is an index. The Postgres documentation recommends using GIN indices for &lt;code&gt;tsvectors&lt;/code&gt; (the reasons why are outside the scope of this post):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;create&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;index&lt;/span&gt; tsv_index &lt;span style=&#34;color:#66d9ef&#34;&gt;on&lt;/span&gt; test_table &lt;span style=&#34;color:#66d9ef&#34;&gt;using&lt;/span&gt; gin (tsv);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will speed up your queries significantly, and is quite important if your tables are large.&lt;/p&gt;
&lt;p&gt;There are several other features you might want to explore. For sake of brevity, I&amp;rsquo;ll just list a couple interesting ones and provide links to the appropriate documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-controls.html#TEXTSEARCH-RANKING&#34; target=&#34;_blank&#34; &gt;rank results&lt;/a&gt; by relevance.&lt;/li&gt;
&lt;li&gt;You can &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-controls.html#TEXTSEARCH-PARSING-DOCUMENTS&#34; target=&#34;_blank&#34; &gt;apply weights to the different columns&lt;/a&gt; that go into your tsvector, for example to make the title more important in ranking than the description.&lt;/li&gt;
&lt;li&gt;You can &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-features.html#TEXTSEARCH-UPDATE-TRIGGERS&#34; target=&#34;_blank&#34; &gt;write custom triggers&lt;/a&gt; to handle more complicated tsvector constructions.&lt;/li&gt;
&lt;li&gt;You can &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-controls.html#TEXTSEARCH-HEADLINE&#34; target=&#34;_blank&#34; &gt;obtain highlighted results&lt;/a&gt; to indicate to the user which part of the document matched their query.&lt;/li&gt;
&lt;li&gt;You can &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES&#34; target=&#34;_blank&#34; &gt;match on word prefixes&lt;/a&gt;, which is useful for implementing a search-box typeahead feature.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s &lt;a href=&#34;https://www.postgresql.org/docs/9.6/static/textsearch-dictionaries.html&#34; target=&#34;_blank&#34; &gt;all kinds of customization&lt;/a&gt; you can do with the dictionaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are many more features I&amp;rsquo;ve omitted here, so don&amp;rsquo;t be afraid to read through that entire chapter of the documentation to learn more.&lt;/p&gt;
&lt;h2 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;There are lots of reasons why we want to avoid substring-based or regex-based searching in our database. Perhaps most importantly, the results are often unintuitive to the user.&lt;/p&gt;
&lt;p&gt;Postgres gives us an easy way to harness powerful fulltext searching using tsvectors and tsqueries. There&amp;rsquo;s absolutely no reason not to use them &amp;ndash; you&amp;rsquo;ll avoid needing to configure a separate system for searching, you&amp;rsquo;ll easily be able to keep your keyword vectors and indices consistent with the latest database contents, and most importantly you&amp;rsquo;ll be giving your users the search results they expect.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Fun with integer division optimizations.</title>
      <link>https://lo.calho.st/posts/integer-division-optimization/</link>
      <pubDate>Tue, 21 Feb 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/integer-division-optimization/</guid>
      <description>&lt;p&gt;I recently stumbled across a &lt;a href=&#34;https://zneak.github.io/fcd/2017/02/19/divisions.html&#34; target=&#34;_blank&#34; &gt;post about some crazy optimization&lt;/a&gt; that clang does to divisions by a constant. If you aren&amp;rsquo;t interested in reading it yourself, the summary is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arbitrary integer division is slow.&lt;/li&gt;
&lt;li&gt;Division by powers of 2 is fast.&lt;/li&gt;
&lt;li&gt;Given a divisor $$n$$, the compiler finds some $$a, b$$ such that $$a/2^b$$ approximates $$1/n$$.&lt;/li&gt;
&lt;li&gt;This approximation gives exact results for any 32-bit integer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was interested in seeing just how much faster this seemingly-magic optimization was than the regular &lt;code&gt;div&lt;/code&gt; instruction, so I set up a simple test framework:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;sys/time.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;udiv19&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; arg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; timeval start, finish;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;unsigned&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;long&lt;/span&gt; accum &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; j;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; j &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;; j&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;gettimeofday&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;amp;start, NULL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt;(i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000000&lt;/span&gt;; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;udiv19&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;gettimeofday&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;amp;finish, NULL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        accum &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; ((&lt;span style=&#34;color:#ae81ff&#34;&gt;1000000l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; finish.tv_sec &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; finish.tv_usec) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1000000l&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; start.tv_sec &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; start.tv_usec));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%f microseconds per 1M operations&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, (&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt;) accum &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you don&amp;rsquo;t feel like reading C, the algorithm is basically as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Repeat 1,000 times:
&lt;ul&gt;
&lt;li&gt;Start a stopwatch.&lt;/li&gt;
&lt;li&gt;Repeat 1,000,000 times:
&lt;ul&gt;
&lt;li&gt;Call the assembly function.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stop the stopwatch and add the time elapsed to the total.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spit out the total time elapsed, divided by 1,000. This gives the average time for 1,000,000 calls to the assembly function.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I took the assembly from the other blog post and marked it up to be able to compile it into this scaffolding:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.intel_syntax&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;noprefix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.global&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;udiv19&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;udiv19:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;ecx&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;edi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2938661835&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;imul&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;rax&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;rcx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;shr&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;rax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;sub&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;edi&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;shr&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;edi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;edi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;shr&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also wrote a naive version using the &lt;code&gt;div&lt;/code&gt; instruction (this took a bit of googling to refresh my knowledge of the x86 ABI and instruction set&amp;hellip;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.intel_syntax&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;noprefix&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;.global&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;udiv19&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;udiv19:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;edx&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;ecx&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mov&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;eax&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;edi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;div&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;ecx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;ret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To compile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gcc -o optimized main.c optimized.s 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gcc -o simple main.c simple.s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The results:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./optimized 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1861.276001 microseconds per 1M operations
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./simple 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2783.295898 microseconds per 1M operations
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can clearly see a ~1.5x speedup (at least on my i5-6400). However, there&amp;rsquo;s a caveat: we&amp;rsquo;re only really testing the &lt;em&gt;latency&lt;/em&gt; of the implementation, not its &lt;em&gt;throughput&lt;/em&gt;. Modern processors have sophisticated ways to increase performance by pipelining instructions and executing them out of order. In a real application, this would drastically reduce the impact of the &lt;em&gt;div&lt;/em&gt; instruction&amp;rsquo;s latency. However, it&amp;rsquo;s still interesting to see this optimization technique and prove that it theoretically &lt;em&gt;could&lt;/em&gt; be beneficial, maybe, in some particular instances.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>The problem with Python&#39;s datetime class.</title>
      <link>https://lo.calho.st/posts/python-datetime-problems/</link>
      <pubDate>Sat, 14 Jan 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/python-datetime-problems/</guid>
      <description>&lt;p&gt;This might sound like a strong opinion, but I&amp;rsquo;m just going to put it out there: &lt;strong&gt;Python should make &lt;code&gt;tzinfo&lt;/code&gt; mandatory on all &lt;code&gt;datetime&lt;/code&gt; objects&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To be fair, that&amp;rsquo;s just an overzealous suggestion prompted by my frustration after spending two full days debugging timestamp misbehaviors. There are plenty of practical reasons to keep timezone-agnostic &lt;code&gt;datetime&lt;/code&gt;s around. Some projects will never need timestamp localization, and requiring them to use &lt;code&gt;tzinfo&lt;/code&gt; everywhere will only needlessly complicate things. However, if you think you might &lt;em&gt;ever&lt;/em&gt; need to deal with timezones in your application, then you &lt;em&gt;must&lt;/em&gt; plan to deal with them from the start. My real proposition is that &lt;strong&gt;a team should assess its needs and set internal standards regarding the use of timestamps before beginning a project&lt;/strong&gt;. That&amp;rsquo;s more reasonable, I think.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The problem.&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re handling timestamps in Python, chances are you are using its standard &lt;code&gt;datetime&lt;/code&gt; class. The &lt;code&gt;datetime&lt;/code&gt; honestly has a pretty great feature set: it lets you do arithmetic with dates, stringify dates, etc.; pretty much anything you need to do with a date, &lt;code&gt;datetime&lt;/code&gt; will do for you. However, lots of problems arise when you use &amp;ldquo;naive&amp;rdquo; &lt;code&gt;datetime&lt;/code&gt; objects, i.e., &lt;code&gt;datetime&lt;/code&gt;s without any timezone awareness.&lt;/p&gt;
&lt;p&gt;Python 2.x had a similar problem differentiating between different types of strings. It&amp;rsquo;s a long story, but essentially whether a string contained binary or text, it was still a string. People who knew what they were doing with strings didn&amp;rsquo;t have a problem, but it was far from idiot-proof. In fact, you didn&amp;rsquo;t really need to be an idiot to fall into the trap &amp;ndash; just naive. This caused lots of problems, so eventually Python 3.x decided to make &lt;code&gt;str&lt;/code&gt; and &lt;code&gt;bytes&lt;/code&gt; into totally different things.&lt;/p&gt;
&lt;p&gt;Naivety is also detrimental in the use of the &lt;code&gt;datetime&lt;/code&gt;. The only place where it works as intended, without a hassle, is in an application where you never have to do any kind of localization or timezone conversion. Once you start trying to convert naive &lt;code&gt;datetime&lt;/code&gt;s between timezones, you&amp;rsquo;ll find that you&amp;rsquo;ve been shot in the foot. My personal opinion is that footguns should not exist, or at least not in the standard libraries of high-level languages like Python.&lt;/p&gt;
&lt;p&gt;I wish I could seriously propose that Python eliminate the naive &lt;code&gt;datetime&lt;/code&gt;, but this would only cause problems. Naive &lt;code&gt;datetime&lt;/code&gt;s are great, since they don&amp;rsquo;t ever require you to look at a timezone database (&lt;code&gt;tzdb&lt;/code&gt;). Once you start dealing with timezones, you have to worry about the &lt;code&gt;tzdb&lt;/code&gt; being up to date. If you don&amp;rsquo;t have complete control over the environment your code is running in, then you can expect inconsistent behavior between users. Whether this is a problem depends on the nature of your project, and I&amp;rsquo;m not about to enumerate all the possibilities &amp;mdash; you can weigh the consequences yourself.&lt;/p&gt;
&lt;p&gt;In short, I propose that anyone starting a new project should decide &amp;ndash; at its very beginning &amp;ndash; what to do with timestamps. In most cases, I think that naive &lt;code&gt;datetime&lt;/code&gt;s should be avoided altogether &amp;mdash; explicit timezone information (&lt;code&gt;tzinfo&lt;/code&gt;) should be included absolutely anywhere &lt;code&gt;datetime&lt;/code&gt;s are used. You should use naive &lt;code&gt;datetime&lt;/code&gt;s only if you will never need to convert between timezones, you can&amp;rsquo;t trust users to have an up-to-date &lt;code&gt;tzdb&lt;/code&gt;, &lt;em&gt;and&lt;/em&gt; having inconsistent &lt;em&gt;tzdb&lt;/em&gt;s between users would likely create other problems.&lt;/p&gt;
&lt;p&gt;Unfortunately, I didn&amp;rsquo;t have the foresight to disallow naive &lt;code&gt;datetime&lt;/code&gt;s in my project at its inception; therefore, I ran into a problem two years down the road at which point I had to do &lt;em&gt;a lot&lt;/em&gt; of refactoring. The remainder of this article details the problems I encountered and the subsequent process of eliminating all naive &lt;code&gt;datetime&lt;/code&gt;s from my codebase.&lt;/p&gt;
&lt;h2 id=&#34;the-dilemma&#34;&gt;The dilemma.&lt;/h2&gt;
&lt;p&gt;When I first started using &lt;code&gt;datetime&lt;/code&gt;s I didn&amp;rsquo;t know any better. I simply called &lt;code&gt;datetime.now()&lt;/code&gt; whenever I needed a timestamp. At that time (no pun intended), my app was only displaying times for a single timezone. Eventually, I realized that I should be converting timestamps to users&amp;rsquo; local timezones, and my naivety came back to bite me in the ass.&lt;/p&gt;
&lt;p&gt;If you didn&amp;rsquo;t know already, &lt;code&gt;datetime.now()&lt;/code&gt; gives you the current time in your local timezone. However, it does not have this timezone information attached by default: it gives you a naive &lt;code&gt;datetime&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;I tried to convert one of these naive &lt;code&gt;datetime&lt;/code&gt;s using the &lt;code&gt;pytz&lt;/code&gt; library (which handles timezone magic):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pytz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; datetime &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; now
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;475618&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/New_York&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;localize(now)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;475618&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt; EST&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; day, &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; STD&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Australia/Sydney&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;localize(now)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;475618&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Australia/Sydney&amp;#39;&lt;/span&gt; AEDT&lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; DST&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that my local timezone is MST; however, the &lt;code&gt;datetime&lt;/code&gt; has no idea about this and therefore &lt;em&gt;doesn&amp;rsquo;t actually do any conversion&lt;/em&gt; when I ask for another timezone. All of the &lt;code&gt;datetime&lt;/code&gt;s it returned are the same, except for their attached &lt;code&gt;tzinfo&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;My first idea was to inject my local timezone into all the naive &lt;code&gt;datetime&lt;/code&gt; objects:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/Denver&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; now
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;410761&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;America/Denver&amp;#39;&lt;/span&gt; MST&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; day, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; STD&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/New_York&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;normalize(now)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;410761&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt; EST&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; day, &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; STD&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Australia/Sydney&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;normalize(now)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;410761&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Australia/Sydney&amp;#39;&lt;/span&gt; AEDT&lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; DST&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the conversion works. However, there are also lots of places in my codebase where I&amp;rsquo;m accepting or returning Unix timestamps. If you don&amp;rsquo;t know, Unix timestamps are always UTC. If you don&amp;rsquo;t ask otherwise, &lt;code&gt;datetime&lt;/code&gt; will convert them into local time for you, again without a &lt;code&gt;tzinfo&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; unixtime &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; unixtime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1484432537.234377&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(unixtime)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;234377&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This isn&amp;rsquo;t so bad, we can fix it mostly the same way as the &lt;code&gt;datetime.now()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(unixtime, pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/Denver&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;234377&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;America/Denver&amp;#39;&lt;/span&gt; MST&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; day, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; STD&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can even convert it to another timezone:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(unixtime, pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/New_York&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;2017&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;234377&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;lt;&lt;/span&gt;DstTzInfo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt; EST&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; day, &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;00&lt;/span&gt; STD&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But what if we want to convert a localized &lt;code&gt;datetime&lt;/code&gt; into a Unix timestamp? If you&amp;rsquo;re familiar with the C &lt;code&gt;strftime&lt;/code&gt; API, you&amp;rsquo;ll be tempted to use &lt;code&gt;strftime(&amp;quot;%s&amp;quot;)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1484432862&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That time, we got the correct result. But watch this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time(), pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/Denver&amp;#34;&lt;/span&gt;))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1484433034&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time(), pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/New_York&amp;#34;&lt;/span&gt;))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1484440239&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What&amp;rsquo;s going on here? We created a single Unix timestamp (&lt;code&gt;t&lt;/code&gt;), and converted it to two separate &lt;code&gt;datetime&lt;/code&gt;s in two different timezones. We already know that conversion from Unix time into any timezone works correctly. We should have gotten the same result when we converted back. However, it turns out that &lt;em&gt;you can only convert a &lt;code&gt;datetime&lt;/code&gt; to a Unix timestamp if it is in your local timezone&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Actually, &lt;code&gt;strftime(&amp;quot;%s&amp;quot;)&lt;/code&gt; is unsupported in Python. It ends up just stripping the &lt;code&gt;tzinfo&lt;/code&gt;, thereby creating a naive timestamp in an arbitrary timezone, and calling the C &lt;code&gt;strftime&lt;/code&gt; which assumes it&amp;rsquo;s being given a local timestamp. Obviously this doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;Now how do you create a Unix timestamp the correct way? It&amp;rsquo;s ugly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now(pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;America/Denver&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; (t &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;1970&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;total_seconds()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1484433334.448718&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In short, you need to take a timezone-aware &lt;code&gt;datetime&lt;/code&gt;, subtract the Unix epoch from it (thereby obtaining a &lt;code&gt;timedelta&lt;/code&gt;), and convert it to seconds. Luckily for us, any arithmetic done with timezone-aware &lt;code&gt;datetime&lt;/code&gt;s is automatically converted to UTC.&lt;/p&gt;
&lt;p&gt;Fortunately, it fails if you pass it a naive &lt;code&gt;datetime&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; (t &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;1970&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;total_seconds()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; File &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;&lt;/span&gt;, line &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;module&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;TypeError&lt;/span&gt;: can&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;t subtract offset-naive and offset-aware datetimes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately, I&amp;rsquo;m sure a lot of beginners are still going to get screwed, since the most popular StackOverflow answers for this situation give you incorrect solutions like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; (t &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;1970&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;total_seconds()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1484408399.491469&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It doesn&amp;rsquo;t fail, since both timestamps are naive. However, the result is wrong: since I used my local time, the result is the number of seconds since 1970-1-1 &lt;em&gt;in my timezone&lt;/em&gt;, rather than in UTC.&lt;/p&gt;
&lt;h2 id=&#34;the-solution&#34;&gt;The solution.&lt;/h2&gt;
&lt;p&gt;Upon discovering how difficult it is to do anything nontrivial with timestamps correctly, I decided to eliminate naive &lt;code&gt;datetime&lt;/code&gt;s from my codebase altogether and standardize an API for doing common tasks with timezone-aware &lt;code&gt;datetime&lt;/code&gt;s. This would help prevent other contributors to my project from shooting themselves in the foot (and by extension, shooting me).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;timehelper&lt;/code&gt; class I created is meant to be used any time you want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the current time,&lt;/li&gt;
&lt;li&gt;Localize and format a timestamp,&lt;/li&gt;
&lt;li&gt;Parse a Unix timestamp, or&lt;/li&gt;
&lt;li&gt;Create a Unix timestamp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any use of the builtin &lt;code&gt;datetime&lt;/code&gt; functions to do these things will now result in a failed code review, because they&amp;rsquo;re all nearly impossible to get right.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;timehelper&lt;/code&gt; itself is very simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; psycopg2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; datetime &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;timehelper&lt;/span&gt;(object):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@staticmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;localize_and_format&lt;/span&gt;(tz, fmt, dt):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# disallow naive datetimes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; dt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tzinfo &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ValueError&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Passed datetime object has no tzinfo&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# workaround for psycopg2 tzinfo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; isinstance(dt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tzinfo, psycopg2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;FixedOffsetTimezone):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      dt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_utcoffset &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_offset
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timezone(tz)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;normalize(dt)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(fmt)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@staticmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;utcnow()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;replace(tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@staticmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;to_posix&lt;/span&gt;(dt):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (dt &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; datetime(&lt;span style=&#34;color:#ae81ff&#34;&gt;1970&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, tzinfo&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC))&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;total_seconds()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;@staticmethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;from_posix&lt;/span&gt;(p):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fromtimestamp(p, pytz&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;UTC)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Its usage is simple, too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of calling &lt;code&gt;datetime.now()&lt;/code&gt;, just call &lt;code&gt;timehelper.now()&lt;/code&gt;. You&amp;rsquo;ll automatically be given a timezone-aware UTC &lt;code&gt;datetime&lt;/code&gt;. The goal of this is to use UTC everywhere within the codebase.&lt;/li&gt;
&lt;li&gt;To convert from a Unix timestamp to a UTC &lt;code&gt;datetime&lt;/code&gt;, use &lt;code&gt;timehelper.from_posix()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To convert from a &lt;code&gt;datetime&lt;/code&gt; to a Unix timestamp, use &lt;code&gt;timehelper.to_posix()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To localize a timestamp to a timezone and format it at the same time, use &lt;code&gt;timehelper.localize_and_format()&lt;/code&gt;. I decided to always localize and format together in order to help enforce the goal of using UTC everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might notice that there&amp;rsquo;s some special magic in the &lt;code&gt;localize_and_format()&lt;/code&gt; method for dealing with &lt;code&gt;tzinfo&lt;/code&gt; objects created by &lt;code&gt;psycopg2&lt;/code&gt;. For some reason, its API has a slight mismatch against that of &lt;code&gt;pytz&lt;/code&gt;. If you aren&amp;rsquo;t using &lt;code&gt;psycopg2&lt;/code&gt;, you can strip out that &lt;code&gt;if&lt;/code&gt; statement. But if you are, make sure all the timestamp-containing columns in PostgreSQL are declared as &lt;code&gt;timestamp with time zone&lt;/code&gt;, rather than simply &lt;code&gt;timestamp&lt;/code&gt;. This is another footgun; traditionally, Postgres used timezones implicitly, but this was reverted in order to comply with SQL standards.&lt;/p&gt;
&lt;h2 id=&#34;the-conclusion&#34;&gt;The conclusion.&lt;/h2&gt;
&lt;p&gt;It took me several hours of research to figure out how to properly deal with timestamps in Python. Its &lt;code&gt;datetime&lt;/code&gt; API is full of gotchas, and a naive developer can easily succumb to its apathy. It turns out that I had many subtle bugs in my codebase before I revisited all code pertaining to timestamps.&lt;/p&gt;
&lt;p&gt;As it&amp;rsquo;s unlikely that naive &lt;code&gt;datetime&lt;/code&gt;s will ever actually be removed from Python, I recommend that everyone create standards for &lt;code&gt;datetime&lt;/code&gt; manipulation within their projects. Doing so may prevent tricky bugs and large rewrites later on.&lt;/p&gt;
&lt;p&gt;If you happen to stumble upon this article in your own search for &lt;code&gt;datetime&lt;/code&gt; incantations, feel free to use my above &lt;code&gt;timehelper&lt;/code&gt; class. Consider it public domain.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Using bcache to back a SSD with a HDD on Ubuntu.</title>
      <link>https://lo.calho.st/posts/using-bcache-to-back-a-ssd-with-a-hdd-on-ubuntu/</link>
      <pubDate>Fri, 06 Jan 2017 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/using-bcache-to-back-a-ssd-with-a-hdd-on-ubuntu/</guid>
      <description>&lt;p&gt;Recently, another student asked me to set up a PostgreSQL instance that they could use for some data mining. I initially put the instance on a HDD, but the dataset was quite large and the import was incredibly slow. I installed the only SSD I had available (120 GB), and it sped up the import for the first few tables. However, this turned out to not be enough space.&lt;/p&gt;
&lt;p&gt;I did not want to move the database permanently back to the HDD, as this would mean slow I/O. I also was not about to go buy another SSD. I had heard of bcache, a Linux kernel module that lets a SSD act as a cache for a larger HDD. This seemed like the most appropriate solution &amp;ndash; most of the data would fit in the SSD, but the backing HDD would be necessary for the rest of it. This article explains how to set up a bcache instance in this scenario. This tutorial is written for Ubuntu Desktop 16.04.1 (Xenial), but it likely applies to more recent versions as well as Ubuntu Server.&lt;/p&gt;
&lt;h2 id=&#34;preparation&#34;&gt;Preparation&lt;/h2&gt;
&lt;p&gt;If you have any existing data on the SSD or HDD, back it up elsewhere. Remove any associated mounts from &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t already have it installed, you need to &lt;code&gt;sudo apt-get install bcache-tools&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;partitions&#34;&gt;Partitions&lt;/h2&gt;
&lt;p&gt;On my machine, the HDD is &lt;code&gt;/dev/sdb&lt;/code&gt; and the SSD is &lt;code&gt;/dev/sdc&lt;/code&gt;. With bcache, you can either use entire disks or individual partitions. In my case, I&amp;rsquo;m using just one HDD partition, &lt;code&gt;/dev/sdb5&lt;/code&gt;, but allowing the entire SSD to be used. Note that the backing HDD or partition has to be at least as large as the caching SSD or partition.&lt;/p&gt;
&lt;p&gt;Surely your setup is different, so replace &lt;code&gt;/dev/sdb5&lt;/code&gt; with your HDD partition, and &lt;code&gt;/dev/sdc&lt;/code&gt; with your caching SSD or partition.&lt;/p&gt;
&lt;p&gt;I gave 250 GiB to &lt;code&gt;/dev/sdb5&lt;/code&gt;; no partitioning is necessary on &lt;code&gt;/dev/sdc&lt;/code&gt; if you are using the entire drive for caching.&lt;/p&gt;
&lt;p&gt;You will need to remove any existing filesystem on both devices/partitions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo wipefs -a /dev/sdc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo wipefs -a /dev/sdb5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is necessary because bcache will refuse to instantiate if it looks like a filesystem already exists on the device.&lt;/p&gt;
&lt;h2 id=&#34;creating-the-bcache&#34;&gt;Creating the bcache&lt;/h2&gt;
&lt;p&gt;A bcache instance looks and acts like a regular block device; instead of being named &lt;code&gt;/dev/sdXX&lt;/code&gt; like a disk partition, it will be named &lt;code&gt;/dev/bcacheX&lt;/code&gt;. The bcache kernel module will handle the underlying hardware and magic of the bcache device, we just have to set it up once.&lt;/p&gt;
&lt;p&gt;We will be using &amp;ldquo;writeback&amp;rdquo; cache mode to enhance write performance; note that this is less safe than the default &amp;ldquo;writethrough&amp;rdquo; mode. If you&amp;rsquo;re worried about this, omit the &lt;code&gt;--writeback&lt;/code&gt; flag. We will also enable the TRIM functionality of the SSD, to further enhance long-term write performance. If your SSD does not support TRIM, omit the &lt;code&gt;--discard&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;The bcache device can be optimized for your disk sector size and your SSD erase block size. In this case, my HDD has 4 KB sectors. I was unable to find the erase block size of my SSD, so I am using the default; however, if you know it you can append, for example, &lt;code&gt;--bucket 2M&lt;/code&gt;, if your erase block size is 2 MB. Similarly, you should change the HDD sector size in this command if you know it, or remove the &lt;code&gt;--block 4k&lt;/code&gt; flag if you don&amp;rsquo;t.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo make-bcache -C /dev/sdc -B /dev/sdb5 --block 4k --discard --writeback
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you should see that the device &lt;code&gt;/dev/bcache0&lt;/code&gt; has been created.&lt;/p&gt;
&lt;h2 id=&#34;create-and-mount-the-filesystem&#34;&gt;Create and mount the filesystem&lt;/h2&gt;
&lt;p&gt;Now, we need to format our new bcache device. I&amp;rsquo;ll be using ext4.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo mkfs.ext4 /dev/bcache0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now add the appropriate fstab line. I&amp;rsquo;ll be mounting the bcache device on &lt;code&gt;/home/postgres&lt;/code&gt; since that&amp;rsquo;s where my PostgreSQL installation previously lived. Another good place for general use would be, for example, &lt;code&gt;/media/bcache&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, you will need to create an empty directory for the mountpoint:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo mkdir -p /home/postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, open &lt;code&gt;/etc/fstab&lt;/code&gt; in your favorite editor (as root, of course) and append the corresponding line (altering the mount options as necessary):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/dev/bcache0 /home/postgres ext4 defaults,noatime,rw 0 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can test that we can mount the new device:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo mount /home/postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The device and mount should both persist through reboot. You may copy any data back to the bcache device at this time.&lt;/p&gt;
&lt;h2 id=&#34;acknowledgments&#34;&gt;Acknowledgments&lt;/h2&gt;
&lt;p&gt;This article was adapted from the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.ubuntu.com/ServerTeam/Bcache&#34; target=&#34;_blank&#34; &gt;https://wiki.ubuntu.com/ServerTeam/Bcache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://wiki.archlinux.org/index.php/Bcache&#34; target=&#34;_blank&#34; &gt;https://wiki.archlinux.org/index.php/Bcache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    
    
    <item>
      <title>Parallelizing single-threaded batch jobs using Python&#39;s multiprocessing library.</title>
      <link>https://lo.calho.st/posts/parallel-batch-jobs-with-python-multiprocessing/</link>
      <pubDate>Fri, 23 Dec 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/parallel-batch-jobs-with-python-multiprocessing/</guid>
      <description>&lt;p&gt;Suppose you have to run some program with 100 different sets of parameters. You might automate this job using a bash script like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ARGS&lt;span style=&#34;color:#f92672&#34;&gt;=(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-foo 123&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-bar 456&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-baz 789&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; a in &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;ARGS[@]&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  my-program $a
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem with this type of construction in bash is that only one process will run at a time. If your program isn&amp;rsquo;t already parallel, you can speed up execution by running multiple jobs at a time. This isn&amp;rsquo;t easy in bash, but fortunately Python&amp;rsquo;s &lt;code&gt;multiprocessing&lt;/code&gt; library makes it quite simple.&lt;/p&gt;
&lt;p&gt;One of the most powerful features in &lt;code&gt;multiprocessing&lt;/code&gt; is the &lt;code&gt;Pool&lt;/code&gt;. You specify the number of concurrent processes you want, a function representing the entry point of the process, and a list of inputs you need evaluated. The inputs are then mapped onto the processes in the &lt;code&gt;Pool&lt;/code&gt;, one batch at a time.&lt;/p&gt;
&lt;p&gt;You can combine this feature with a &lt;code&gt;subprocess&lt;/code&gt; call to invoke an external program. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; subprocess&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; multiprocessing&lt;span style=&#34;color:#f92672&#34;&gt;,&lt;/span&gt; functools
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ARGS &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-foo 123&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-bar 456&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-baz 789&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NUM_CORES &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shell &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; functools&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;partial(subprocess&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call, shell&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pool &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; multiprocessing&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Pool(NUM_CORES)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map(shell, [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-program &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; a &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ARGS])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To break it down, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Translated the &lt;code&gt;ARGS&lt;/code&gt; array from the bash script to Python syntax,&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;functools.partial&lt;/code&gt; to give us a helper function that invokes &lt;code&gt;subprocess.call(..., shell=True)&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Created a pool of &lt;code&gt;NUM_CORES&lt;/code&gt; processes,&lt;/li&gt;
&lt;li&gt;Used list comprehension to prepend the program name to each element in the &lt;code&gt;ARGS&lt;/code&gt; list,&lt;/li&gt;
&lt;li&gt;And finally mapped the resulting list onto the process pool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is that your program will be executed with each specified set of arguments, parallelized over &lt;code&gt;NUM_CORES&lt;/code&gt; processes. There&amp;rsquo;s only two more lines of code than the bash script, but the performance benefit can be manyfold.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>The fruits of some recent Arduino mischief.</title>
      <link>https://lo.calho.st/posts/arduino-mischief/</link>
      <pubDate>Wed, 21 Dec 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/arduino-mischief/</guid>
      <description>&lt;p&gt;I recently consulted on a project involving embedded devices. Like most early-stage embedded endeavors, it currently consists of an Arduino and a bunch of off-the-shelf peripherals. During the project, I developed two small libraries (unrelated to the main focus of the project) which I&amp;rsquo;m open-sourcing today.&lt;/p&gt;
&lt;h2 id=&#34;library-1-a-generic-median-filter-implementation&#34;&gt;Library 1: A generic median filter implementation.&lt;/h2&gt;
&lt;p&gt;The first library I&amp;rsquo;m releasing is a simple, generic median filter. Median filters are useful for smoothing noise out of data, but unfortunately I couldn&amp;rsquo;t find a good existing library and was forced to write my own. Because I had to apply median filters to several types of data, I implemented it as a template class.&lt;/p&gt;
&lt;p&gt;Note that while I developed the library for the Arduino use case, it is actually just plain ol&amp;rsquo; C++ and will work literally anywhere.&lt;/p&gt;
&lt;p&gt;Github link: &lt;a href=&#34;https://github.com/tmick0/generic_median&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/generic_median&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;library-2-a-simple-driver-for-a-poorly-documented-chinese-rfid-module&#34;&gt;Library 2: A simple driver for a poorly-documented Chinese RFID module.&lt;/h2&gt;
&lt;p&gt;Next up, we have a barebones driver for DFRobot&amp;rsquo;s ID01 UHF RFID reader. The sample code released by the vendor is a joke, and the manual is even worse. To put the module to use, I had to reverse-engineer the frame format for tag IDs. I don&amp;rsquo;t think anyone else should ever have to go through this, so I&amp;rsquo;m publishing the library here.&lt;/p&gt;
&lt;p&gt;Github link: &lt;a href=&#34;https://github.com/tmick0/dfrobot_rfid&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/dfrobot_rfid&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Refer to the READMEs on the Github repos for more information about both libraries.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Optimizing MySQL and Apache for a low-memory VPS.</title>
      <link>https://lo.calho.st/posts/mysql-apache-low-memory/</link>
      <pubDate>Mon, 17 Oct 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/mysql-apache-low-memory/</guid>
      <description>&lt;h2 id=&#34;diagnosing-the-problem&#34;&gt;Diagnosing the problem.&lt;/h2&gt;
&lt;p&gt;My last post had a plug about the migration of our Wordpress instance to a new server. However, it didn&amp;rsquo;t go completely smoothly. The site had gone down a few times in the first day after the migration, with Wordpress throwing &amp;ldquo;Error establishing a database connection.&amp;rdquo; Sure enough, MySQL had gone down. A simple restart of MySQL would bring the site back up, but what caused the crash in the first place?&lt;/p&gt;
&lt;p&gt;A peek into &lt;code&gt;/var/log/mysql/error.log&lt;/code&gt; yielded this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;2016-10-12T21:20:50.588667Z 0 [ERROR] InnoDB: mmap(137428992 bytes) failed; errno 12
2016-10-12T21:20:50.588702Z 0 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
2016-10-12T21:20:50.588728Z 0 [ERROR] InnoDB: Plugin initialization aborted with error Generic error
2016-10-12T21:20:50.588749Z 0 [ERROR] Plugin &amp;#39;InnoDB&amp;#39; init function returned error.
2016-10-12T21:20:50.588758Z 0 [ERROR] Plugin &amp;#39;InnoDB&amp;#39; registration as a STORAGE ENGINE failed.
2016-10-12T21:20:50.588767Z 0 [ERROR] Failed to initialize plugins.
2016-10-12T21:20:50.588772Z 0 [ERROR] Aborting
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So it looks like an out-of-memory error. This VPS only has 512 MB of RAM, so I wouldn&amp;rsquo;t be surprised. Clearly, some tuning would be necessary. First, we&amp;rsquo;ll reduce the size of MySQL&amp;rsquo;s buffer pool, then shrink Apache&amp;rsquo;s worker pool, and finally add a swapfile just in case memory pressure remains a problem.&lt;/p&gt;
&lt;h2 id=&#34;optimizing-mysql&#34;&gt;Optimizing MySQL.&lt;/h2&gt;
&lt;p&gt;The error that we saw was for the allocation of a buffer pool for InnoDB, one of MySQL&amp;rsquo;s storage engines. We can see from the log that it&amp;rsquo;s trying to allocate somewhere around 128 MB using &lt;code&gt;mmap&lt;/code&gt;. This corresponds to the default value of the &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; configuration option. Let&amp;rsquo;s go ahead and trim this down to about 20 MB &amp;ndash; it&amp;rsquo;ll reduce MySQL&amp;rsquo;s performance, but we don&amp;rsquo;t have much of a choice on a machine this small.&lt;/p&gt;
&lt;p&gt;On Ubuntu, I put this option in &lt;code&gt;/etc/mysql/mysqld.conf.d/mysqld.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;innodb_buffer_pool_size = 20M
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Issue &lt;code&gt;sudo service mysql restart&lt;/code&gt;, and rejoice as MySQL no longer uses 25% of your RAM.&lt;/p&gt;
&lt;h2 id=&#34;optimizing-apache&#34;&gt;Optimizing Apache.&lt;/h2&gt;
&lt;p&gt;Most of Apache&amp;rsquo;s memory usage comes from the fact that it preemptively forks worker processes in order to handle requests with low latency. This is handled by the &lt;code&gt;mpm_prefork&lt;/code&gt; module, and if enabled, its config can be found in &lt;code&gt;/etc/apache2/mods-enabled/mpm_prefork.conf&lt;/code&gt; (on Ubuntu, at least).&lt;/p&gt;
&lt;p&gt;By default, Apache will create 5 processes at startup, keep a minimum of 5 idle processes at all times, allow up to 10 idle processes before they&amp;rsquo;re reaped, and spawn up to 256 processes at a time under load. Let&amp;rsquo;s reduce these to something more sane given the constraints of our system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;IfModule&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;mpm_prefork_module&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; StartServers &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; MinSpareServers &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; MaxSpareServers &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; MaxRequestWorkers &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; MaxConnectionsPerChild &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;/IfModule&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, sudo service apache2 restart` and you&amp;rsquo;re done.&lt;/p&gt;
&lt;h2 id=&#34;creating-a-swapfile&#34;&gt;Creating a swapfile.&lt;/h2&gt;
&lt;p&gt;Most VPSes don&amp;rsquo;t give you a swap partition by default, like you would probably create on a dedicated server or your desktop. We can create one using a file on an existing filesystem, in order to make sure there&amp;rsquo;s extra virtual memory available in case our tuning doesn&amp;rsquo;t handle everything perfectly.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s pre-allocate space in the filesystem. We can do this using the &lt;code&gt;fallocate&lt;/code&gt; command. I made a 2 GB swapfile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo fallocate -l 2G /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, give it some sane permissions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;600&lt;/span&gt; /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, format the file so it looks like a swap filesystem:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo mkswap /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally, tell the OS to use it as swap space:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo swapon /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we&amp;rsquo;ve got swap for the time being, but it won&amp;rsquo;t persist when we reboot the system. To make it persist, simply add a line to &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/swapfile none swap sw 0 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Congratulations, you&amp;rsquo;re now the proud owner of some swap space.&lt;/p&gt;
&lt;p&gt;Hopefully this post will help anyone suffering stability problems with MySQL and Apache on a small VPS. I&amp;rsquo;ve adapted the instructions here from several sources, most notably &lt;a href=&#34;http://stackoverflow.com/questions/12114746/mysqld-service-stops-once-a-day-on-ec2-server/12683951#12683951&#34; target=&#34;_blank&#34; &gt;this StackOverflow post&lt;/a&gt;, and &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-16-04&#34; target=&#34;_blank&#34; &gt;this article from DigitalOcean&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Information-centric networking for laymen.</title>
      <link>https://lo.calho.st/posts/information-centric-networking/</link>
      <pubDate>Sat, 08 Oct 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/information-centric-networking/</guid>
      <description>&lt;p&gt;The design of the current Internet is based on the concept of connections between &amp;ldquo;hosts&amp;rdquo;, or individual computers. For example, when you visit a website, your computer (a host) always connects to a particular server (another host) and retrieves content through a session-oriented pipe. However, the amount of content hosted on the Internet and the number of connected devices are both growing. This is a crisis scenario for the current Internet architecture &amp;ndash; it won&amp;rsquo;t scale.&lt;/p&gt;
&lt;p&gt;Several proposals for Next-Generation Network (NGN) architectures have been proposed in recent years, aimed at better handling immense amounts of traffic and orders of magnitude more pairwise connections. Information-Centric Networking (ICN) is one NGN paradigm which eschews the concept of connections entirely, removing the host as the basic &amp;ldquo;unit&amp;rdquo; of the network and replacing it with content objects.&lt;/p&gt;
&lt;p&gt;In other words, the defining feature of an ICN is that instead of asking the network to connect you to a particular server (where you may hope to find a content you desire), you instead ask the network for the content itself.&lt;/p&gt;
&lt;p&gt;Several distinct ICN architectures have been proposed, however for the remainder of this article I will focus on Named-Data Networking (NDN) and Content-Centric Networking (CCN), the two most popular designs in recent literature. NDN and CCN both share the core concept of consumer-driven communication, wherein a consumer (or client) issues an Interest packet (a request) for a content object and hopes to receive a Data packet in return. Interest and Data packets are both identified by a Name, which is in essence a immutable, human-readable name for a particular content object.&lt;/p&gt;
&lt;p&gt;Whereas current Internet routers rely on only one lookup table (i.e., a forwarding table) in order to route packets toward a destination, NDN/CCN routers use three main data structures in order to locate content objects. A Pending Interest Table (PIT) keeps track of outstanding requests, a Content Store (CS) caches content objects, and a Forwarding Information Base (FIB) stores default forwarding information.&lt;/p&gt;
&lt;p&gt;When a router receives an Interest, it will first check its CS to see if it can serve the content immediately from its cache. If it is not found there, then the router checks its PIT to see if there is an outstanding request for the same content; if there is then the request does not need to be forwarded again, since the data for the previous request can satisfy both that request and the new one. Finally, if an existing Interest is not found, the router checks the FIB for a route toward the appropriate content provider; once a route has been identified, the Interest is forwarded and the PIT is updated.&lt;/p&gt;
&lt;p&gt;Though ICN requires routers to store more state and make more complicated forwarding decisions, it is still expected to reduce the overall network load by virtue of Interest aggregation and content caching. Caching in particular also benefits the end-user, since the availability of content nearby reduces download time. Since content downloads are independent of any particular connection, ICN also allows multi-RAT (Radio Access Technology) communication to be exploited by mobile devices, further improving the user&amp;rsquo;s QoE (Quality-of-Experience).&lt;/p&gt;
&lt;p&gt;Last week, I presented a collaborative caching scheme for NDN at ACM ICN 2016, the leading conference in the ICN domain (&lt;a href=&#34;http://conferences2.sigcomm.org/acm-icn/2016/slides/Session3/mick.pdf&#34; target=&#34;_blank&#34; &gt;slides&lt;/a&gt;, &lt;a href=&#34;http://conferences2.sigcomm.org/acm-icn/2016/proceedings/p93-mick.pdf&#34; target=&#34;_blank&#34; &gt;paper&lt;/a&gt;) which is able to satisfy up to 20% of Interests without leaving the home ISP&amp;rsquo;s network. Additionally, we published an article in IEEE Communications Magazine about the advantages of ICN for mobile networks (&lt;a href=&#34;https://lo.calho.st/papers/COMG_20160901_Sep_2016.pdf&#34; &gt;paper&lt;/a&gt;). These works, as well as those of the larger ICN community, have the potential to influence the acceptance of ICN as the foundation of the future Internet. Only with continued research will we find a holistic solution for scalability in the face of billions of connected devices and billions of terabytes of traffic.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Why are tuples greater than lists?</title>
      <link>https://lo.calho.st/posts/why-are-tuples-greater-than-lists/</link>
      <pubDate>Mon, 03 Oct 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/why-are-tuples-greater-than-lists/</guid>
      <description>&lt;p&gt;I pose this question in quite a literal sense. Why does Python 2.7 have this behavior?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No matter what the tuple, and no matter what the list, the tuple will always be considered greater. On the other hand, Python 3 gives us an error, which actually makes a bit more sense:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; File &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;&lt;/span&gt;, line &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;module&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;TypeError&lt;/span&gt;: unorderable types: tuple() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The following post is a journey into some CPython internals, with a goal of finding out why 2.7 gives us such a weird comparison result.&lt;/p&gt;
&lt;p&gt;Those of you who have implemented nontrivial classes in Python are probably aware of the two different comparison interfaces in the data model: rich comparison, and simple comparison. Rich comparison is implemented by defining the functions &lt;code&gt;__lt__&lt;/code&gt;, &lt;code&gt;__le__&lt;/code&gt;, &lt;code&gt;__eq__&lt;/code&gt;, &lt;code&gt;__ne__&lt;/code&gt;, &lt;code&gt;__gt__&lt;/code&gt;, and &lt;code&gt;__ge__&lt;/code&gt;. That is, there is one function for each possible comparison operator. Simple comparison uses only one function, &lt;code&gt;__cmp__&lt;/code&gt;, which has a similar interface to C&amp;rsquo;s &lt;code&gt;strcmp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Any comparison operation you write in Python compiles down to the &lt;code&gt;COMPARE_OP&lt;/code&gt; bytecode, which itself is handled by a function called &lt;code&gt;cmp_outcome&lt;/code&gt;. For the types of comparisons we&amp;rsquo;re concerned with today (i.e., inequalities rather than exact comparisons), this function will end up calling &lt;code&gt;PyObject_RichCompare&lt;/code&gt;, the user-facing comparison function in the C API.&lt;/p&gt;
&lt;p&gt;At this point, the runtime will attempt to use the rich comparison interface, if possible. Assuming that neither operand&amp;rsquo;s class is a subclass of the other, the first class&amp;rsquo;s comparison functions will be checked first; the second class would be checked if the first class does not yield a useful result. In the case of &lt;code&gt;tuple&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt;, both calls return &lt;code&gt;NotImplemented&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Having failed to use the rich comparison interface, we now try to call &lt;code&gt;__cmp__&lt;/code&gt;. The actual semantics here are quite complicated, but in the case at hand, all attempts fail. One penultimate effort before hitting the last-ditch &amp;ldquo;default&amp;rdquo; compare function is to convert both operands to numeric types (which fails here, of course).&lt;/p&gt;
&lt;p&gt;CPython&amp;rsquo;s &lt;code&gt;default_3way_compare&lt;/code&gt; is somewhat of a collection of terrible ideas. If the two objects are of the same type, it will try to compare them by address and return that result. Otherwise, we then check if either value is &lt;code&gt;None&lt;/code&gt;, which would be considered smaller than anything else. The second-to-last option, which we will actually end up using in the case of &lt;code&gt;tuple&lt;/code&gt; vs. &lt;code&gt;list&lt;/code&gt;, is to compare the names of the two classes (essentially returning &lt;code&gt;strcmp(v-&amp;gt;ob_type-&amp;gt;tp_name, w-&amp;gt;ob_type-&amp;gt;tp_name)&lt;/code&gt;). Note, however, that any numeric type would have its type name switched to the empty string here, so a number ends up being considered smaller than anything non-numeric. If we end up in a case where both type names are the same (either they actually have the same name, or they are incomparable numeric types), then we get a final result by comparing pointers to the type definitions.&lt;/p&gt;
&lt;p&gt;To validate our findings, consider the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; tuple() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;abc&lt;/span&gt; (tuple):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; abc() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;xyz&lt;/span&gt; (tuple):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; xyz() &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; list()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only difference between classes &lt;code&gt;abc&lt;/code&gt; and &lt;code&gt;xyz&lt;/code&gt; (and &lt;code&gt;tuple&lt;/code&gt;, even) are their names, however we can see that the instances are compared differently. Now, we have certainly found quite the footgun here, so it&amp;rsquo;s fortunate that Python 3 has a more sane comparison operation.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>Quick postfix &amp; dovecot config with virtual hosts (Ubuntu 16.04)</title>
      <link>https://lo.calho.st/posts/postfix-dovecot-config/</link>
      <pubDate>Tue, 23 Aug 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/postfix-dovecot-config/</guid>
      <description>&lt;p&gt;This morning, I received an email from my VPS host notifying me that they will no longer accept PayPal. Instead, my only payment option would be Bitcoin. Not willing to go through this trouble, I decided to migrate from this host (which I had been using for my personal servers for about five years now) to DigitalOcean (which fortunately accepts normal forms of payment).&lt;/p&gt;
&lt;p&gt;Part of my server migration was to move email for two of my domains: le1.ca and lo.calho.st. Setting up a new mailserver is a notoriously arduous task, so I&amp;rsquo;m documenting the process in this post &amp;ndash; mostly for my future reference, but also to benefit anyone who might stumble upon my blog in their own confusion.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m serving mail for two domains, I will be using a simple &amp;ldquo;virtual hosts&amp;rdquo; configuration. I&amp;rsquo;ll talk about the process in four parts: local setup, postfix, dovecot, and DNS configuration.&lt;/p&gt;
&lt;h2 id=&#34;initial-setup&#34;&gt;Initial Setup&lt;/h2&gt;
&lt;p&gt;We must prepare a few things before we&amp;rsquo;re ready to configure the actual mail services. The first thing I&amp;rsquo;d like to do is install a SSL certificate, so that we can access the mail server securely. This is easy with Let&amp;rsquo;s Encrypt:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo apt-get install letsencrypt &lt;span style=&#34;color:#75715e&#34;&gt;# (if you don&amp;#39;t have it installed already)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ letsencrypt certonly --standalone -d mail.le1.ca -d mail.lo.calho.st
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m using the standalone option here, because I do not have an HTTP daemon installed on this server. If you&amp;rsquo;re also serving HTTP from your mail machine, you will need to &lt;a href=&#34;https://certbot.eff.org/&#34; target=&#34;_blank&#34; &gt;follow the appropriate instructions for your configuration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now that we have an SSL cert, we can set up the directory structure for our virtual mailboxes. I&amp;rsquo;m going to make the root for this environment &lt;code&gt;/var/spool/mail&lt;/code&gt;, and in that directory, I&amp;rsquo;m going to create one folder for each domain I&amp;rsquo;m serving:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir -p /var/spool/mail/le1.ca
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir -p /var/spool/mail/lo.calho.st
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The next step is to configure passwords for each of your users. I&amp;rsquo;m doing this the easy way, since I&amp;rsquo;m the only person using the mail server. If you need users to be able to change their own passwords, this is going to be more difficult; you&amp;rsquo;ll need to find an alternate authentication configuration.&lt;/p&gt;
&lt;p&gt;You will need to calculate a password hash for each user you want to serve:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ doveadm pw -u foo@le1.ca
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each time, it should spit out a hash that looks like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{CRAM-MD5}a7cb902940b3f6662c48ace840a4e3e410241e875d720cb45b2d95a3e1ddfc8b
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For each of your domains, create a &lt;code&gt;shadow&lt;/code&gt; file in the corresponding &lt;code&gt;/var/spool/mail/*&lt;/code&gt; directory. For example, I will create &lt;code&gt;/var/spool/mail/le1.ca/shadow&lt;/code&gt;. Add a &lt;code&gt;username:password&lt;/code&gt; line for each virtual user you want to create.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;foo@le1.ca:{CRAM-MD5}a7cb902940b3f6662c48ace840a4e3e410241e875d720cb45b2d95a3e1ddfc8
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, let&amp;rsquo;s create a &amp;ldquo;virtual mail&amp;rdquo; user and configure him as the owner of these mail directories.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo adduser vmail
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo chown -R vmail:vmail /var/spool/mail/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Find out the UID and GID of this new user and remember it for later. An easy way to do this is &lt;code&gt;grep vmail /etc/passwd&lt;/code&gt;. The UID and GID are the third and fourth columns, respectively.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it for initial setup. Now let&amp;rsquo;s deal with postfix.&lt;/p&gt;
&lt;h2 id=&#34;configuring-postfix&#34;&gt;Configuring Postfix&lt;/h2&gt;
&lt;p&gt;Postfix is responsible for delivering received mail to the correct locations on our server, as well as sending mail on our behalf.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s some initial configuration we can do interactively before we start poking around in config files. If you haven&amp;rsquo;t installed postfix yet, this configuration will start automatically when you do &lt;code&gt;apt-get install postfix&lt;/code&gt;. If you do already have it installed, then you can run &lt;code&gt;dpkg-reconfigure postfix&lt;/code&gt; to get there.&lt;/p&gt;
&lt;p&gt;Here are the responses you should enter to this wizard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Type of mail configuration:&lt;/strong&gt; Internet Site&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System mail name:&lt;/strong&gt; Use your &amp;ldquo;primary&amp;rdquo; mail domain here. I used &amp;ldquo;le1.ca&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Root and postmaster recipient:&lt;/strong&gt; Enter your primary email address here. It will be used as the destination for administrative mail.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Destinations to accept mail for:&lt;/strong&gt; Enter any aliases of your machine here, but &lt;em&gt;don&amp;rsquo;t enter the domains you&amp;rsquo;re going to use in your virtual mailboxes.&lt;/em&gt; A good list is something like &lt;code&gt;my-hostname, localhost.localdomain, localhost&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything else:&lt;/strong&gt; Leave it at the default value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The main configuration file for postfix is &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;. Let&amp;rsquo;s break down the config into three sections: SSL, virtual mailboxes, and SASL. I&amp;rsquo;m only including lines that need to be modified here. Everything else should stay at its default value.&lt;/p&gt;
&lt;p&gt;First, SSL-related configs:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;smtpd_tls_cert_file=/etc/letsencrypt/live/[your_mailserver_name]&amp;lt;/strong&amp;gt;/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/[your_mailserver_name]&amp;lt;/strong&amp;gt;/privkey.pem
smtpd_use_tls=yes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Virtual mailbox configs:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;virtual_mailbox_domains = /etc/postfix/virtual_domains
virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox
virtual_mailbox_base = /var/spool/mail
virtual_uid_maps = static:1001 # Replace with the UID of your vmail user
virtual_gid_maps = static:1001 # Replace with the GID of your vmail user
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SASL configs (to pass authentication duties to Dovecot):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain =
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hopefully you noticed that the virtual mailbox configs refer to some new files. We&amp;rsquo;re going to have to create &lt;code&gt;/etc/postfix/virtual_domains&lt;/code&gt; and &lt;code&gt;/etc/postfix/virtual_mailbox&lt;/code&gt; in order to create the mappings for our domains.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;virtual_domains&lt;/code&gt; file is easy, just a list of the domains you&amp;rsquo;re serving. In my case:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;le1.ca
lo.calho.st
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;virtual_mailbox&lt;/code&gt; file is also pretty easy. It just maps email addresses to mailbox folders, relative to the &lt;code&gt;virtual_mailbox_base&lt;/code&gt; directory. For example, if I want both &lt;a href=&#34;mailto:foo@le1.ca&#34; &gt;foo@le1.ca&lt;/a&gt; and &lt;a href=&#34;mailto:bar@le1.ca&#34; &gt;bar@le1.ca&lt;/a&gt; to go to the mailbox &lt;code&gt;le1.ca/foobar&lt;/code&gt;, and &lt;a href=&#34;mailto:baz@lo.calho.st&#34; &gt;baz@lo.calho.st&lt;/a&gt; to go to &lt;code&gt;lo.calho.st/baz&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;foo@le1.ca le1.ca/foobar/
bar@le1.ca le1.ca/foobar/
baz@lo.calho.st lo.calho.st/baz/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can create as many aliases as you want here. Whatever is on the right will end up determining your login username (in this case, &lt;a href=&#34;mailto:foobar@le1.ca&#34; &gt;foobar@le1.ca&lt;/a&gt; and &lt;a href=&#34;mailto:baz@lo.calho.st&#34; &gt;baz@lo.calho.st&lt;/a&gt; are my usernames), and the addresses on the left create aliases.&lt;/p&gt;
&lt;p&gt;Postfix now requires us to hash this map file. Run the following command from within &lt;code&gt;/etc/postfix&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ postmap virtual_mailbox
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should create a file called &lt;code&gt;virtual_mailbox.db&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The last part of the postfix config is to enable the &amp;ldquo;user-facing&amp;rdquo; SMTP port, 587. Just paste this block in &lt;code&gt;master.cf&lt;/code&gt; somewhere:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_sasl_security_options=noanonymous
-o smtpd_sasl_local_domain=$myhostname
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_login_maps=hash:/etc/postfix/virtual_mailbox
-o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The next step is to configure dovecot.&lt;/p&gt;
&lt;h2 id=&#34;configuring-dovecot&#34;&gt;Configuring Dovecot&lt;/h2&gt;
&lt;p&gt;Dovecot is much easier to configure than postfix. Create &lt;code&gt;/etc/dovecot/dovecot.conf&lt;/code&gt; with the following content (delete it if it already exists):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;protocols = imap
log_path = /var/log/dovecot.log

ssl = required
ssl_cert = &amp;lt;/etc/letsencrypt/live/[your_mailserver_name]/fullchain.pem
ssl_key = &amp;lt;/etc/letsencrypt/live/[your_mailserver_name]/privkey.pem
ssl_dh_parameters_length=2048
ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL

disable_plaintext_auth = no
mail_location = maildir:/var/spool/mail/%d/%n

auth_verbose = yes
auth_mechanisms = plain login

passdb {
 driver = passwd-file
 args = /var/spool/mail/%d/shadow
}

userdb {
 driver = static
 args = uid=vmail gid=vmail home=/var/spool/mail/%d/%n
}

protocol lda {
 postmaster_address = root@le1.ca
}

service auth {
 unix_listener /var/spool/postfix/private/auth {
 mode = 0660
 user = postfix
 group = postfix
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now you should be ready to restart postfix and dovecot:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo service dovecot restart
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo service postfix restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If everything went smoothly, we&amp;rsquo;re ready to configure DNS records.&lt;/p&gt;
&lt;h2 id=&#34;dns-records&#34;&gt;DNS Records&lt;/h2&gt;
&lt;p&gt;For each of your domains, you will need three DNS records:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Either a CNAME or an A record pointing to the mailserver&lt;/li&gt;
&lt;li&gt;An MX record designating the mailserver as the destination for the domain&amp;rsquo;s mail&lt;/li&gt;
&lt;li&gt;A SPF record designating the server as a permitted origin for mail&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The A record is easy. Just create an A record for &amp;ldquo;mail.yourdomain.com&amp;rdquo; pointing to the IP address of the server.&lt;/p&gt;
&lt;p&gt;The MX record might be even easier: set the mailserver for &amp;ldquo;yourdomain.com&amp;rdquo; to &amp;ldquo;mail.yourdomain.com&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The SPF record is also very straightforward. Many people overlook its importance, but without it, mail from your domain is likely to go into recipients&amp;rsquo; spam folders. There exist &lt;a href=&#34;http://www.spfwizard.net/&#34; target=&#34;_blank&#34; &gt;wizards to create custom SPF records&lt;/a&gt;, but I find the one below to be sufficient for most purposes. Just insert this as a TXT record for &amp;ldquo;yourdomain.com&amp;rdquo;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#34;v=spf1 mx a ptr ~all&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;rsquo;s it. Allow a few minutes for your DNS records to propagate, then test out your new mailserver.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>An easy way to visualize git activity</title>
      <link>https://lo.calho.st/posts/git-visualization/</link>
      <pubDate>Sat, 11 Jun 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/git-visualization/</guid>
      <description>&lt;p&gt;Today, I wrote &lt;a href=&#34;https://github.com/tmick0/gitply&#34; target=&#34;_blank&#34; &gt;gitply&lt;/a&gt; &amp;ndash; a fairly simple Python script for visualizing the weekly activity of each contributor to a git repository.&lt;/p&gt;
&lt;p&gt;It started out as a run-once script to get some statistics for one of my projects, but I ended up improving it incrementally until it turned into something friendly enough for other people to use.&lt;/p&gt;
&lt;h2 id=&#34;what-it-does&#34;&gt;What it does:&lt;/h2&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;&lt;img src=&#34;example-e1465711414387.png&#34; alt=&#34;example of gitply output&#34;&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll get a graph like this for each contributor. What all do we see here?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lines inserted (green bars, left axis, log scale)&lt;/li&gt;
&lt;li&gt;Lines deleted (red bars, left axis, log scale)&lt;/li&gt;
&lt;li&gt;Commits made (blue line, right axis, linear scale)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also get a textual representation of the same data:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;History for anon2
  2016, week  6:  2 commits, +366  -26  
  2016, week  7:  4 commits, +325  -5   
  2016, week  8:  2 commits, +224  -3   
  2016, week  9: 21 commits, +2617 -219 
  -- Gap of 2 weeks
  2016, week 12: 10 commits, +4066 -614 
  2016, week 13:  6 commits, +2480 -432 
  2016, week 14:  2 commits, +654  -490 
  -- Gap of 1 week
  2016, week 16:  2 commits, +661  -229 
  -- Gap of 2 weeks
  2016, week 19:  3 commits, +1508 -47  
  -- Gap of 2 weeks
  2016, week 22:  1 commits, +1490 -29  

History for anon1
  2016, week 12:  1 commits, +143  -89  
  2016, week 13:  2 commits, +58   -16  
  2016, week 14:  3 commits, +137  -17  
  2016, week 15:  2 commits, +403  -106 
  -- Gap of 2 weeks
  2016, week 18:  5 commits, +134  -38  
  -- Gap of 3 weeks
  2016, week 22:  1 commits, +13   -1   
  2016, week 23:  1 commits, +485  -1   
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you just want to download it and use it already, &lt;a href=&#34;https://github.com/tmick0/gitply&#34; target=&#34;_blank&#34; &gt;check out the project on GitHub&lt;/a&gt;. For those who are interested, some implementation details follow.&lt;/p&gt;
&lt;h2 id=&#34;how-does-it-work&#34;&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;The first thing gitply does is invoke the standard git client to retrieve a log:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git log --numstat --date&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;short --no-notes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, it analyzes the log line-by-line in real time (thanks, Python iterators) in order to get the data we need. Essentially, we create an iterator that yields tuples of (email, date, stats) for each commit, where stats is the pair (insertions, deletions).&lt;/p&gt;
&lt;p&gt;Once we create this iterator over the commit history, we can attribute each commit to a user and ultimately fill in a dictionary that will help us print the analysis. We sum the insertions, deletions, and commits for each ISO week, then iterate through the weeks and print out the final results.&lt;/p&gt;
&lt;p&gt;Now, if for some reason you need an iterator over the commits in a repository, you can import my &lt;code&gt;iterate_commits&lt;/code&gt; function from this script and use it yourself.&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;Right now, gitply supports aggregating statistics from several repositories into one report. One planned feature is to use stacked bars and lines to show the amount of activity per-project, per-user, per-week. Until then, the data from multiple repositories will all be displayed together.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Adventures in image glitching</title>
      <link>https://lo.calho.st/posts/image-glitching/</link>
      <pubDate>Thu, 09 Jun 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/image-glitching/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Databending&#34; target=&#34;_blank&#34; &gt;Databending&lt;/a&gt; is a type of &lt;a href=&#34;https://en.wikipedia.org/wiki/Glitch_art&#34; target=&#34;_blank&#34; &gt;glitch art&lt;/a&gt; wherein image files are intentionally corrupted in order to produce an aesthetic effect. Traditionally, these effects are produced by manually manipulating the compressed data in an image file. As a result, this is a trial-and-error process; often, edits will result in the file being completely corrupted and unopenable.&lt;/p&gt;
&lt;p&gt;Someone recently asked me whether I knew why databending different types of image files produces different effects &amp;ndash; and particularly, why PNG glitches are the most interesting. I didn&amp;rsquo;t know the answer, but the question inspired me to do a little research (mostly reading the Wikipedia articles about the compression techniques used in &lt;a href=&#34;https://en.wikipedia.org/wiki/JPEG&#34; target=&#34;_blank&#34; &gt;different&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/Portable_Network_Graphics&#34; target=&#34;_blank&#34; &gt;image&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/GIF&#34; target=&#34;_blank&#34; &gt;formats&lt;/a&gt;). I discovered that most compression techniques are not all that different. Most of them just employ some kind of run-length encoding or dictionary encoding, and then a prefix-free coding step. The subtle differences between the compression algorithms could not explain the wildly different effects we observed (except for in JPEGs, perhaps, since the compression is done in the frequency domain). However, PNG used a pre-filtering step which made it stand out.&lt;/p&gt;
&lt;p&gt;PNG&amp;rsquo;s pre-filtering is basically a heuristic to try to make the image data more compressible. It tries to translate each pixel of the image into some delta based on pixels in the previous row or column. Since the core compression algorithms have no idea about the two-dimensional nature of an image, this two-dimensional filtering step actually makes a pretty big impact.&lt;/p&gt;
&lt;p&gt;There are &lt;a href=&#34;https://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering&#34; target=&#34;_blank&#34; &gt;five main line filters&lt;/a&gt; defined by the PNG format. Most PNG encoders will try to optimize their choices of filters for each line in the image, in order to reduce the compressed size of the image. However, each filter also yields its own unique glitches.&lt;/p&gt;
&lt;p&gt;Thus, an idea was spawned, and I wrote a Python script that glitches images not by manipulating their raw representations, but by applying one of the PNG filters and modifying the pixels in that representation.&lt;/p&gt;
&lt;h2 id=&#34;png-filter-glitches&#34;&gt;PNG Filter Glitches&lt;/h2&gt;
&lt;p&gt;Since this article is about glitching &amp;ndash; not image compression &amp;ndash; we&amp;rsquo;re going to focus on the different visual effects we get from each filter. Usually, PNG codecs alternate between filters for each line, however for the sake of demonstration I configured my script to use one filter throughout the whole image.&lt;/p&gt;
&lt;p&gt;All of the following glitches are made from a scaled-down version of &lt;a href=&#34;https://alpha.wallhaven.cc/wallpaper/361124&#34; target=&#34;_blank&#34; &gt;this wallpaper from Wallhaven&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two of the simplest PNG filters are the Up filter and the Sub filter. While Up creates a delta from the adjacent pixel in the previous row, Sub creates a delta from the previous pixel in the same row. The results of glitching on the filters are similar &amp;ndash; Up gives us vertical lines, and Sub gives us horizontal lines.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_up.jpg&#34; alt=&#34;Glitch result from the Up filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Glitch result from the Up filter&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_sub.jpg&#34; alt=&#34;Glitch result from the Sub filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Glitch result from the Sub filter&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A more interesting one is the Average filter. This filter looks at the both the above-pixel and the left-pixel and takes their average, then computes a delta using that value. Glitching on the Average filter results in smears of altered color that slowly defuse away across the image.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_avg.jpg&#34; alt=&#34;Glitch result from the Average filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Average&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My favorite is actually the Paeth filter, which heuristically chooses either the above-pixel, left-pixel, or above-and-to-the-left pixel as the basis for the delta, depending on a function of all three. Glitches applied to this filter give us rectangular disturbances which slowly fade out, however stack together to create harsher effects.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_paeth.jpg&#34; alt=&#34;Glitch result from the Paeth filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Paeth&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I mentioned that there are five filters, but so far I&amp;rsquo;ve only shown four. The reason for this is simple &amp;ndash; the fifth filter is the Null filter, which has no effect whatsoever.&lt;/p&gt;
&lt;h2 id=&#34;mixing-filters&#34;&gt;Mixing Filters&lt;/h2&gt;
&lt;p&gt;To simulate the mixed effects one can observe in a typical PNG glitch, I implemented a glitching procedure which uses a random filter on each line. The results are pretty intriguing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_randlines.jpg&#34; alt=&#34;Random line filters&#34;&gt;&lt;br&gt;
&lt;em&gt;Random line filters&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this example, I&amp;rsquo;ve only allowed a 5% chance for the filter to change from one line to the next; without this provision, the results are quite boring.&lt;/p&gt;
&lt;p&gt;To make the glitches even cooler, I started stacking filters on top of each other. Here&amp;rsquo;s an example with an extra pass through the Average filter applied after the randomized filtering stage:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_randlines1.jpg&#34; alt=&#34;Randomized + Average&#34;&gt;&lt;br&gt;
&lt;em&gt;Randomized + Average&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;glitch-glitches&#34;&gt;Glitch Glitches&lt;/h2&gt;
&lt;p&gt;Overall, my favorite filters were the ones that had bugs when I wrote them. My original implementation of the Paeth filter had an error due to the use of unsigned integers in the predictor (when really I needed signed ones). However, this version resulted in some cool effects that acted like a combination of the normal Paeth and Average behaviors:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_badpaeth.jpg&#34; alt=&#34;Buggy Paeth filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Buggy Paeth filter&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also wrote a buggy Average filter that resulted in some bold vertical lines and some subtler horizontal artifacts:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_badaverage1.jpg&#34; alt=&#34;Buggy Average filter&#34;&gt;&lt;br&gt;
&lt;em&gt;Buggy Average filter&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;try-it-yourself&#34;&gt;Try it Yourself&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href=&#34;https://github.com/tmick0/ppg&#34; target=&#34;_blank&#34; &gt;posted my glitch scripts on GitHub&lt;/a&gt;, so you can try them out too. The original version was pure Python and incredibly slow, but I&amp;rsquo;ve now converted the core functionality into a C module and it is several orders of magnitude faster. This is also my first foray into the intricacies of the Python C API, but that&amp;rsquo;s a topic for another post&amp;hellip;&lt;/p&gt;
&lt;p&gt;To conclude, have two more glitches achieved by mixing various filters:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_test-2.jpg&#34; alt=&#34;glitched image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;glitch_test-1.jpg&#34; alt=&#34;glitched image&#34;&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>What&#39;s inside a PEM file?</title>
      <link>https://lo.calho.st/posts/whats-inside-a-pem-file/</link>
      <pubDate>Thu, 28 Apr 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/whats-inside-a-pem-file/</guid>
      <description>&lt;p&gt;As you probably know, PEM is a base64-encoded format with human-readable headers, so you can kind of figure out what you&amp;rsquo;re looking at if you open it in a text editor.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s look at an RSA public key:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4YFgwNrEkMdynjtDsM0q
b+Hedk8p4pySfxakYTfSQPEyGxxnQGcVMV2ZEjPR4nZeqJrtNTlixhK2YWqunE6I
KopVDq3WvtPKweNEeZ8B2lA2I8FFrpZSjI/Tosq8/MbTd/Y/C4Q8Qcz78MF/NH17
/E82K3ca9/LM2b4KGTEIhsLUff7OGrJM7lPcQZN3EOdUeQnzT9uTh8Z9oFqChfJP
pLwwSebfrRB7VMXjeKHZmubSO5pULHLdZLbkgLSmnhbgBjO6apG0tkYyOeWd6L8F
MzA21WkXJdANrr1s/yv5zS9hx1q9jSM8Me9QA2/iaAbgem7VwQ2YlPiXEvUq48oB
VsKXMpHQ6A2cUygs+PiSFuUzNjTIebWFTWmKKuoRx0O2m63fAZJaT2aJA4G0HqdJ
ZQ2Aqr4Acs1+28IhLxUbMAlHJ4N2XPnE2WpQYbtUR4zZMXU+bVIToXuqHCLo4pf/
qEIK/xzr/S8WdvMvRVSOtVIIQwyaMDUxsnnKozYSVHvzYsxQo3b3VD5OOqmg1mx1
+Z/PLFViLkBjo+ZMkl5dFbsgYyHmkn/uvCV19IpjkdDNfFgdrOlSdNTnlGU7su5L
L31k/IwSvD0PR0egxiv8HhegaYwqgujVylB0gntyBsrVVHfE3Wr2+aJlR3YmrdCZ
lsAiSbnFxgGtfB6INHepFdkCAwEAAQ==
-----END PUBLIC KEY-----
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that we have a public key just from the header. But what&amp;rsquo;s all the base64 stuff? If it&amp;rsquo;s a public key, we know that there should be a modulus and an exponent hidden in there. And there&amp;rsquo;s also probably some kind of hint that this is an RSA key, as opposed to some other type of key.&lt;/p&gt;
&lt;p&gt;That base-64 payload actually has the potential to contain a lot of information. It&amp;rsquo;s an ASN.1 (Abstract Syntax Notation One) data structure, encoded in a format called DER (Distinguished Encoding Rules), and then finally base64-encoded before the header and footer are attached.&lt;/p&gt;
&lt;p&gt;ASN.1 is used for a lot of stuff besides keys and certificates; it is a generic file format that can be used to serialize any kind of hierarchical data. DER is just one of many encoding formats for an ASN.1 structure &amp;ndash; e.g. there is an older format called BER (Basic Encoding Rules) and an XML-based format called XER (you can probably guess what that stands for).&lt;/p&gt;
&lt;p&gt;But anyway, what is inside that public key? How can we find out?&lt;/p&gt;
&lt;h2 id=&#34;parsing-pem-files&#34;&gt;Parsing PEM files&lt;/h2&gt;
&lt;p&gt;OpenSSL comes with a utility called asn1parse. It can understand plain DER or PEM-encoded ASN.1. Let&amp;rsquo;s feed our public key into it and take a look:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ openssl asn1parse -in public.pem -i
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    0:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;546&lt;/span&gt; cons: SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    4:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt; cons:  SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    6:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt; prim:   OBJECT            :rsaEncryption
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   17:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; prim:   NULL              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   19:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;527&lt;/span&gt; prim:  BIT STRING
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The column that contains &amp;ldquo;cons&amp;rdquo; and &amp;ldquo;prim&amp;rdquo; gives us information about the hierarchical structure of the data. &amp;ldquo;Cons&amp;rdquo; stands for a &amp;ldquo;constructed&amp;rdquo; field, i.e., a field that encapsulates other fields; on the other hand, &amp;ldquo;prim&amp;rdquo; means &amp;ldquo;primitive.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The column after &amp;ldquo;cons&amp;rdquo; or &amp;ldquo;prim&amp;rdquo; tells us what type of data is in that field. The -i flag I&amp;rsquo;ve supplied causes that column to be indented according to how deep we are in the hierarchical structure. So overall, what are we looking at?&lt;/p&gt;
&lt;p&gt;There is one root SEQUENCE object. That SEQUENCE contains another SEQUENCE and a BIT STRING. That internal SEQUENCE has an OBJECT and a NULL terminator. The OBJECT field is actually an Object Identifier &amp;ndash; it contains some constant that tells us what kind of object we&amp;rsquo;re decoding. As we may have suspected, it tells us that we&amp;rsquo;re decoding an RSA key.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s where stuff gets really interesting: That BIT STRING field actually contains more ASN.1 data. Let&amp;rsquo;s jump in and parse it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ openssl asn1parse -in public.pem -strparse &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt; -i 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    0:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;522&lt;/span&gt; cons: SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    4:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;513&lt;/span&gt; prim:  INTEGER           :E18160C0DAC490C7729E...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  521:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; prim:  INTEGER           :010001
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(The -strparse 19 flag means &amp;ldquo;parse the data in the field located at offset 19 in the original structure.&amp;rdquo; If there was another BIT STRING inside this one, we could add another -strparse argument to recurse into it.)&lt;/p&gt;
&lt;p&gt;What do we have here? A SEQUENCE containing two INTEGERs. It turns out that the first INTEGER is our modulus, and the second INTEGER is the exponent.&lt;/p&gt;
&lt;p&gt;You can actually infer the effective length of the key from this information. The third column indicates the length of the modulus is 513 bytes; one of these bytes is just padding, so that means our key is 512 bytes (or 4096 bits) in strength.&lt;/p&gt;
&lt;h2 id=&#34;reverting-to-der&#34;&gt;Reverting to DER&lt;/h2&gt;
&lt;p&gt;As I explained earlier, PEM is just a wrapper around DER. So can we dump the raw DER and parse it that way? You sure can. You just cut out the PEM headers and base64-decode the payload, and what comes out is DER.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ grep -v &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;PUBLIC KEY&amp;#34;&lt;/span&gt; public.pem | openssl base64 -d &amp;gt; public.der
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can inspect the raw DER:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ hexdump public.der 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000000&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8230&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2202&lt;/span&gt; 0d30 &lt;span style=&#34;color:#ae81ff&#34;&gt;0906&lt;/span&gt; 862a &lt;span style=&#34;color:#ae81ff&#34;&gt;8648&lt;/span&gt; 0df7 &lt;span style=&#34;color:#ae81ff&#34;&gt;0101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000010&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0501&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0300&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0282&lt;/span&gt; 000f &lt;span style=&#34;color:#ae81ff&#34;&gt;8230&lt;/span&gt; 0a02 &lt;span style=&#34;color:#ae81ff&#34;&gt;8202&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0102&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000020&lt;/span&gt; e100 &lt;span style=&#34;color:#ae81ff&#34;&gt;6081&lt;/span&gt; dac0 90c4 72c7 3b9e b043 2acd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000030&lt;/span&gt; e16f 76de 294f 9ce2 7f92 a416 &lt;span style=&#34;color:#ae81ff&#34;&gt;3761&lt;/span&gt; 40d2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000040&lt;/span&gt; 32f1 1c1b &lt;span style=&#34;color:#ae81ff&#34;&gt;4067&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1567&lt;/span&gt; 5d31 &lt;span style=&#34;color:#ae81ff&#34;&gt;1299&lt;/span&gt; d133 76e2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000050&lt;/span&gt; a85e ed9a &lt;span style=&#34;color:#ae81ff&#34;&gt;3935&lt;/span&gt; c662 b612 6a61 9cae 884e
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000060&lt;/span&gt; 8a2a 0e55 d6ad d3be c1ca 44e3 9f79 da01
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0000070&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3650&lt;/span&gt; c123 ae45 &lt;span style=&#34;color:#ae81ff&#34;&gt;5296&lt;/span&gt; 8f8c a2d3 bcca c6fc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or, we can use asn1parse if we actually want to understand it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ openssl asn1parse -in public.der -inform DER -i
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    0:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;546&lt;/span&gt; cons: SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    4:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt; cons:  SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    6:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt; prim:   OBJECT            :rsaEncryption
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   17:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; prim:   NULL              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   19:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;527&lt;/span&gt; prim:  BIT STRING   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ openssl asn1parse -in public.der -inform DER -strparse &lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt; -i
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    0:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;522&lt;/span&gt; cons: SEQUENCE          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    4:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;513&lt;/span&gt; prim:  INTEGER           :E18160C0DAC490C7729E...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  521:d&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;  hl&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; l&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;   &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; prim:  INTEGER           :010001
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the exact same data we saw earlier, but there&amp;rsquo;s your proof that PEM really is just DER underneath a layer of base64.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>My first adventure with Let&#39;s Encrypt on nginx, dovecot, and postfix</title>
      <link>https://lo.calho.st/posts/letsencrypt-nginx-dovecot-postfix/</link>
      <pubDate>Mon, 28 Mar 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/letsencrypt-nginx-dovecot-postfix/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://letsencrypt.org&#34; target=&#34;_blank&#34; &gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; is old news by now. It launched back in December, so it has been giving away free DV certificates for nearly four months now. Being a TA for a Computer Security course, it&amp;rsquo;s about time that I actually tried it out.&lt;/p&gt;
&lt;h2 id=&#34;prologue&#34;&gt;Prologue&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s Encrypt is a free certificate authority. They grant TLS certificates that you can use to secure your webserver. They are Domain Validated (DV) certificates, which means they will verify that you control the domain name you are trying to certify.&lt;/p&gt;
&lt;p&gt;To test out Let&amp;rsquo;s Encrypt, I decided to deploy new certificates on some internal Zeall.us services (Gitlab and Roundcube). Previously, we were using self-signed certs, so we would have to approve a security exception on every visit.&lt;/p&gt;
&lt;p&gt;Unfortunately, I chose the wrong day to test out Let&amp;rsquo;s Encrypt. For some reason, their DNS client decided to die at the very moment I tried to get a certificate. I &lt;a href=&#34;https://community.letsencrypt.org/t/dns-problem-query-timed-out-looking-up-a/13354/2&#34; target=&#34;_blank&#34; &gt;wasn&amp;rsquo;t the only one having this problem&lt;/a&gt;, but Google hadn&amp;rsquo;t yet crawled the support site so I didn&amp;rsquo;t see these other reports until the problem had already subsided. Once it cleared up, everything else was a breeze.&lt;/p&gt;
&lt;h2 id=&#34;obtaining-certificates&#34;&gt;Obtaining certificates&lt;/h2&gt;
&lt;p&gt;Obtaining certificates for our Gitlab and webmail instances was incredibly easy. I just cloned the &lt;a href=&#34;https://github.com/letsencrypt/letsencrypt&#34; target=&#34;_blank&#34; &gt;Let&amp;rsquo;s Encrypt repository&lt;/a&gt; from Github and used their super-simple command-line utility. It doesn&amp;rsquo;t have built-in support for nginx (our webserver of choice), but you can still easily use the webroot plugin to validate your domain. You just tell it the directory from which your webserver serves, and it puts a file there which it uses for the validation. I ran it once for each of the services:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./letsencrypt-auto certonly --email example@example.com --text --agree-tos --renew-by-default --webroot -w /opt/gitlab/embedded/service/gitlab-rails/public -d &amp;lt;our_gitlab_domain&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./letsencrypt-auto certonly --email example@example.com --text --agree-tos --renew-by-default --webroot -w /srv/roundcube -d &amp;lt;our_webmail_domain&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It handles everything automatically, and sticks your new certificates and keys in their respective /etc/letsencrypt/live/&amp;lt;domain_name&amp;gt;/ directories.&lt;/p&gt;
&lt;h2 id=&#34;configuring-nginx&#34;&gt;Configuring nginx&lt;/h2&gt;
&lt;p&gt;Prior to setting up the new certificates in nginx, I generated strong Diffie-Hellman key exchange parameters, as suggested by &lt;a href=&#34;https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html&#34; target=&#34;_blank&#34; &gt;this guide to nginx hardening&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem &lt;span style=&#34;color:#ae81ff&#34;&gt;4096&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I just put the following lines in each of my nginx site configs:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssl on;
ssl_certificate /etc/letsencrypt/live/&amp;lt;domain&amp;gt;/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/&amp;lt;domain&amp;gt;/privkey.pem;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers &amp;#39;EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH&amp;#39;;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Follow up with a &lt;code&gt;sudo service nginx reload&lt;/code&gt;, and you&amp;rsquo;re good to go. With this configuration, my domain got an A+ from &lt;a href=&#34;https://www.ssllabs.com/ssltest/&#34; target=&#34;_blank&#34; &gt;Qualys&amp;rsquo; SSL Labs Server Test&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;configuring-dovecot&#34;&gt;Configuring Dovecot&lt;/h2&gt;
&lt;p&gt;To supply the new certificate to my Dovecot IMAP server, I set the following configs in &lt;code&gt;/etc/dovecot/conf.d/10-ssl.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssl_cert = &amp;lt;/etc/letsencrypt/live/&amp;lt;domain&amp;gt;/fullchain.pem
ssl_key = &amp;lt;/etc/letsencrypt/live/&amp;lt;domain&amp;gt;/privkey.pem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the same certificate I used for webmail, since the domain is the same. I have not yet investigated hardening techniques for Dovecot, but at a glance the settings seem to be quite similar to nginx.&lt;/p&gt;
&lt;p&gt;This configuration was followed up with a &lt;code&gt;sudo service dovecot restart&lt;/code&gt;, and then everything worked.&lt;/p&gt;
&lt;h2 id=&#34;configuring-postfix&#34;&gt;Configuring Postfix&lt;/h2&gt;
&lt;p&gt;Finally, I supplied the same certificate to Postfix (SMTP) by putting the following in &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;smtpd_tls_cert_file = /etc/letsencrypt/live/&amp;lt;domain&amp;gt;/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/&amp;lt;domain&amp;gt;/privkey.pem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a &lt;code&gt;sudo service postfix restart&lt;/code&gt;, the certificates are live.&lt;/p&gt;
&lt;h2 id=&#34;auto-renew&#34;&gt;Auto-renew&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s Encrypt provides certificates with a term of only 90 days, so it is necessary to write a script to renew them automatically. I wrote the following script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/opt/letsencrypt/letsencrypt-auto renew | tee -a ./lerenew.log
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;service nginx reload
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;service postfix reload
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;service dovecot reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And a corresponding crontab:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0 0 * * 0 ~/lerenew.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now once a week, any eligible certificates will be renewed and the services which use them will be reloaded.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It has never been this easy to deploy TLS certificates. I highly recommend that anyone who is currently using a self-signed certificate switch to Let&amp;rsquo;s Encrypt. If you&amp;rsquo;re currently paying for a DV certificate, consider switching to Let&amp;rsquo;s Encrypt to save some money &amp;ndash; but keep compatibility issues in mind.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Demonstrating the double-DES meet-in-the-middle attack</title>
      <link>https://lo.calho.st/posts/double-des-meet-in-the-middle/</link>
      <pubDate>Thu, 04 Feb 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/double-des-meet-in-the-middle/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;DES (Data Encryption Standard) is an old-school block cipher which has been around since the 1970s. It only uses a 56-bit key, which is undeniably too short for use in the modern day. Between the realization that DES is weak in the late 90s and the invention of AES in the early 2000&amp;rsquo;s, Triple-DES had a brief time to shine.&lt;/p&gt;
&lt;p&gt;Triple-DES is just what it sounds like: you run the DES algorithm three times. You use two 56-bit keys, &lt;strong&gt;K1&lt;/strong&gt; and &lt;strong&gt;K2&lt;/strong&gt;, and apply them in the order &lt;strong&gt;K1&lt;/strong&gt;-&lt;strong&gt;K2&lt;/strong&gt;-&lt;strong&gt;K1&lt;/strong&gt;. The result is a cipher with 112 bits of key strength.&lt;/p&gt;
&lt;p&gt;Students often ask me, why not just encrypt twice: &lt;strong&gt;K1&lt;/strong&gt;, &lt;strong&gt;K2&lt;/strong&gt;, done? The reason is that this construction is vulnerable to a particular chosen-plaintext attack, which we call the meet-in-the-middle attack. That is, if the attacker knows your plaintext in addition to your ciphertext, he doesn&amp;rsquo;t have to try all $2^{112}$ keys. The maximum amount of work he has to perform is actually only $2^{56}$ &amp;ndash; not much more than to break single DES.&lt;/p&gt;
&lt;h2 id=&#34;notation&#34;&gt;Notation&lt;/h2&gt;
&lt;p&gt;Before I proceed to an explanation of the meet-in-the-middle attack, let&amp;rsquo;s get some basic cryptography notation out of the way.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s call our plaintext &lt;strong&gt;P&lt;/strong&gt;, and our ciphertext &lt;strong&gt;C&lt;/strong&gt;. Let&amp;rsquo;s call our DES encryption function &lt;strong&gt;E&lt;/strong&gt;. It takes in a plaintext and a key; in return, it produces a ciphertext. Similarly, there is a decryption function &lt;strong&gt;D&lt;/strong&gt; which takes a ciphertext and a key and spits out a plaintext.&lt;/p&gt;
&lt;p&gt;We can then say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;E(P, K) = C&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D(C, K) = P&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D(E(P, K), K) = P&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E(D(C, K), K) = C&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You get the idea. Let&amp;rsquo;s move on.&lt;/p&gt;
&lt;h2 id=&#34;double-des&#34;&gt;Double-DES&lt;/h2&gt;
&lt;p&gt;Double DES uses two keys, &lt;strong&gt;K1&lt;/strong&gt; and &lt;strong&gt;K2&lt;/strong&gt; We produce a ciphertext using the following formula:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C = E(E(P, K1), K2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We just encrypt twice, using our two keys. Easy.&lt;/p&gt;
&lt;p&gt;Since each key is 56 bits, an attacker that doesn&amp;rsquo;t know the plaintext would need to guess a total of 112 bits of key to break the cipher. Therefore, the total keyspace is $2^{112}$, and an average of $2^{111}$ keys would have to be guessed before the true &lt;strong&gt;K1&lt;/strong&gt; and &lt;strong&gt;K2&lt;/strong&gt; are obtained.&lt;/p&gt;
&lt;h2 id=&#34;meet-in-the-middle&#34;&gt;Meet-in-the-middle&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s redefine Double-DES as a two-step process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;M = E(P, K1)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C = E(M, K2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We will refer to the result of the first encryption as &lt;strong&gt;M&lt;/strong&gt;, as it is our &amp;ldquo;middle&amp;rdquo; value. Now, for this attack to work, we assume our adversary has access to both &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;C&lt;/strong&gt;, but wants to determine &lt;strong&gt;K1&lt;/strong&gt; and &lt;strong&gt;K2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider the following statement &amp;ndash; we can easily derive it from the second formula we wrote above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;M = D(C, K2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An attacker can easily exploit this fact, and essentially make &lt;strong&gt;K1&lt;/strong&gt; independent of &lt;strong&gt;K2&lt;/strong&gt;. He can guess them each separately, instead of trying to guess them at the same time. But how?&lt;/p&gt;
&lt;p&gt;First, the attacker guesses every possible &lt;strong&gt;K1&lt;/strong&gt;. Let&amp;rsquo;s call each of his guesses &lt;strong&gt;K1&amp;rsquo;&lt;/strong&gt;. For each guess, he computes &lt;strong&gt;M&amp;rsquo; = E(P, K1&amp;rsquo;)&lt;/strong&gt; to obtain a potential &amp;ldquo;middle&amp;rdquo; value, and stores the result in a table along with the corresponding &lt;strong&gt;K1&amp;rsquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;After filling out the table, with each of the $2^{56}$ possible &lt;strong&gt;K1&amp;rsquo;&lt;/strong&gt; keys, the attacker moves on to guessing &lt;strong&gt;K2&lt;/strong&gt;. Again, let&amp;rsquo;s refer to each guess as &lt;strong&gt;K2&amp;rsquo;&lt;/strong&gt; He computes &lt;strong&gt;M&amp;rsquo; = D(C, K2&amp;rsquo;)&lt;/strong&gt; then checks whether this &lt;strong&gt;M&amp;rsquo;&lt;/strong&gt; matches any &lt;strong&gt;M&amp;rsquo;&lt;/strong&gt; he stored in the table previously.&lt;/p&gt;
&lt;p&gt;If a match is found, then both keys have been broken. That is, if &lt;strong&gt;E(P, K1&amp;rsquo;) = D(C, K2&amp;rsquo;)&lt;/strong&gt;, then &lt;strong&gt;K1 = K1&amp;rsquo;&lt;/strong&gt; and &lt;strong&gt;K2 = K2&amp;rsquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;How much work has the attacker done? Well, generating the table required $2^{56}$ encryptions, then finding a match would require $2^{55}$ decryptions on average. Overall, we will only have done only $2^{56.6}$ cryptographic operations. This is in stark contrast to the $2^{111}$ operations which we would expect to perform in a ciphertext-only attack with two 56-bit keys.&lt;/p&gt;
&lt;h2 id=&#34;pseudocode&#34;&gt;Pseudocode&lt;/h2&gt;
&lt;p&gt;In short, this is the attack:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;meet_in_the_middle&lt;/span&gt;(C, P):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  table &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;56&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    table[E(P, i)] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;56&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; D(C, i) &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; table:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; table[D(C, i)], i
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it.&lt;/p&gt;
&lt;h2 id=&#34;demonstration&#34;&gt;Demonstration&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve written a quick demonstration of this attack in Python using the pycrypto library. The source code is &lt;a href=&#34;https://github.com/tmick0/class-resources/blob/master/ddes/ddes.py&#34; target=&#34;_blank&#34; &gt;on my Github&lt;/a&gt;. For the demo, I have only used 18 bits of the key (just so you don&amp;rsquo;t have to wait forever to see the results). There are also some optimizations which can be made (e.g., DES takes the key as a 64-bit input even though only 56 of those bits are used&amp;hellip; we can skip some of the inputs that we have tested to make it faster). However, it works.&lt;/p&gt;
&lt;h2 id=&#34;triple-des&#34;&gt;Triple-DES&lt;/h2&gt;
&lt;p&gt;So why isn&amp;rsquo;t Triple-DES vulnerable to this attack? Well, it&amp;rsquo;s easy. We can only &amp;ldquo;skip&amp;rdquo; one encryption using this strategy. Since Triple-DES is defined as &lt;strong&gt;E(D(E(P,K1),K2),K1)&lt;/strong&gt;, we can&amp;rsquo;t split it up in such a way that we won&amp;rsquo;t, at some point, have to guess both &lt;strong&gt;K1&lt;/strong&gt; and &lt;strong&gt;K2&lt;/strong&gt; at the same time. A formal proof of this claim is, unfortunately, out of scope of this article.&lt;/p&gt;
&lt;h2 id=&#34;last-word&#34;&gt;Last word&lt;/h2&gt;
&lt;p&gt;This attack isn&amp;rsquo;t specific to DES. It works on any composition of two ciphers which is constructed as &lt;strong&gt;E(E(P, K1), K2)&lt;/strong&gt;. So don&amp;rsquo;t ever, ever, ever try to do that.&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>A fun experiment with Twilio</title>
      <link>https://lo.calho.st/posts/twilio-experiment/</link>
      <pubDate>Sat, 09 Jan 2016 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/twilio-experiment/</guid>
      <description>&lt;p&gt;I first heard about &lt;a href=&#34;https://www.twilio.com/&#34; target=&#34;_blank&#34; &gt;Twilio&lt;/a&gt; a long, long time ago. As Google Voice faded out of relevance, it took the lead in the &lt;em&gt;mobile-communication-as-a-service&lt;/em&gt; market. However, I had never had the chance (or inclination) to play around with its API until today.&lt;/p&gt;
&lt;p&gt;About 12 hours after we landed back in the US from our holiday in Mexico, Lynsey departed once again &amp;ndash; this time to the Plant and Animal Genome conference (PAG) in San Diego. She asked me to supply her with pictures of our cats for the duration of her trip. I told her I would send her a cat pic every hour, on the hour.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t realize what I had gotten myself into until I had already deposited $20 into a new Twilio account and spent 2 hours coding away&amp;hellip; Though my goal was just to send some photos of cats, I had developed a &lt;a href=&#34;http://github.com/tmick0/twiliq&#34; target=&#34;_blank&#34; &gt;pretty general application&lt;/a&gt; that lets you build a queue of MMSes to be disseminated at a constant rate.&lt;/p&gt;
&lt;p&gt;This was not only my first experiment with Twilio &amp;ndash; it was also my first adventure with Flask, a framework for building simple HTTP services in Python. I must say that both Flask and Twilio were extremely easy to use, and I would not hesitate to use them again.&lt;/p&gt;
&lt;p&gt;I will certainly be finding some more uses for Twilio in the future &amp;ndash; it will be invaluable for the SMS-based 2FA we plan to integrate into &lt;a href=&#34;http://zeall.us&#34; target=&#34;_blank&#34; &gt;Zeall&lt;/a&gt;. But for the time being, it will be serving as a medium for the delivery of photos of cats.&lt;/p&gt;
&lt;p&gt;If anyone is interested in my Twilio-based app, here is a link again: &lt;a href=&#34;https://github.com/tmick0/twiliq&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/twiliq&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>No, fingerprints are not secure</title>
      <link>https://lo.calho.st/posts/fingerprints-are-not-secure/</link>
      <pubDate>Wed, 02 Dec 2015 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/fingerprints-are-not-secure/</guid>
      <description>&lt;p&gt;Authentication is the process by which a system determines whether a particular user is allowed to access it. There are three widely agreed-upon methods to authenticate a user:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Something you have.&lt;/li&gt;
&lt;li&gt;Something you know.&lt;/li&gt;
&lt;li&gt;Something you are.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you use your key to unlock your front door, you are authenticating yourself using something you have. In information security, passwords are the most popular method of authentication; they are something you know. Authentication by something you are (i.e., biometrics) has historically been only a niche practice, but in recent years it has caught on in the realm of consumer electronics.&lt;/p&gt;
&lt;p&gt;When Apple announced Touch ID in late 2013, security experts immediately voiced their concern. The authentication mechanism was &lt;a href=&#34;http://www.ccc.de/en/updates/2013/ccc-breaks-apple-touchid&#34; target=&#34;_blank&#34; &gt;quickly compromised&lt;/a&gt;, and there is still very little that Apple can do about it. Why, you ask? Because fingerprints are inherently insecure.&lt;/p&gt;
&lt;p&gt;An authentication mechanism must meet three major requirements to be considered secure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is secret.&lt;/li&gt;
&lt;li&gt;It is unique.&lt;/li&gt;
&lt;li&gt;It is revocable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While fingerprints are unique (in theory &amp;ndash; more on that later), they are neither secret nor revocable.&lt;/p&gt;
&lt;p&gt;You wear your fingerprints every day, and everybody can see them. But what implications could this possibly have? About a year ago, a German hacker famously announced that he had &lt;a href=&#34;http://www.theguardian.com/technology/2014/dec/30/hacker-fakes-german-ministers-fingerprints-using-photos-of-her-hands&#34; target=&#34;_blank&#34; &gt;compromised the fingerprint of defense minister Ursula von der Layen&lt;/a&gt; using only a photograph. After obtaining a photograph of someone&amp;rsquo;s fingerprint, it is trivial to create a fake. And that easily, your biometric security has been defeated &amp;ndash; permanently.&lt;/p&gt;
&lt;p&gt;Permanently? Yes &amp;ndash; biometrics aren&amp;rsquo;t revocable. If someone steals your password, you can easily change it. If they steal the keys to your house, you can change the locks. But if someone steals your fingerprint, you can&amp;rsquo;t get new fingers. Just take a minute and let that sink in.&lt;/p&gt;
&lt;p&gt;Now, more on the uniqueness aspect. We are all taught that our fingerprints are unique, which is essentially true. However, the way they are interpreted by computers is not exactly unique. When a fingerprint is used for authentication, an algorithm typically selects a handful of features it thinks are unique and distinct; these points are called &lt;em&gt;minutia&lt;/em&gt; and are the only portions of the fingerprint considered for identity matching.&lt;/p&gt;
&lt;p&gt;Because the environment will not always be the same when your fingerprint is scanned, each read will probably contain a different set of minutia. Whether it be caused by random noise, damage to the imaging lens, or something on your finger (like some dirt, or perhaps a burn or a cut), two scans will never be exactly the same. Thus, fingerprints can not be compared for an exact match the way that passwords are.&lt;/p&gt;
&lt;p&gt;What are the implications of this kind of comparison? Well, it means there must be some &lt;em&gt;acceptance threshold&lt;/em&gt; which determines how &amp;ldquo;alike&amp;rdquo; two fingerprints must be to be considered a match. And thus there will always be a chance that someone else&amp;rsquo;s fingerprint will be able to authenticate your identity. We can always raise the threshold to reduce this probability, but it will in turn make it more likely that a valid fingerprint will be rejected. In the current state of the art, &lt;a href=&#34;http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.211.1363&amp;amp;rep=rep1&amp;amp;type=pdf&#34; target=&#34;_blank&#34; &gt;a false positive rate (FPR) of 0.1% can correspond to a false negative rate (FNR) anywhere between 0.02% and 15%&lt;/a&gt;. Standard consumer devices probably lie on the higher end of that FNR range. I doubt that users would tolerate getting rejected 15% of the time, so it would be a safe bet to assume that the threshold is set so that the false positive rate is much higher than 1 in 1000.&lt;/p&gt;
&lt;p&gt;So what have we learned here? Well, that&amp;rsquo;s up to you to decide. In my opinion, the integration of fingerprint scanners into consumer devices is nothing more than a shifty marketing scheme. The general public thinks biometrics are super high-tech, and thus they are a means to inspire consumers to invest in a new gadget.&lt;/p&gt;
&lt;p&gt;If you have any sensitive data on your phone, I suggest you at least put it behind a PIN. I reiterate, your fingerprint can be trivially stolen. It might be a good choice for second authentication mechanism (i.e., for two-factor authentication), but it should not be used by itself.&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Time clocking at the command line</title>
      <link>https://lo.calho.st/posts/command-line-time-clock/</link>
      <pubDate>Wed, 02 Sep 2015 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/command-line-time-clock/</guid>
      <description>&lt;p&gt;I often feel inclined to start new projects to avoid working on old ones. In a particularly ironic display of procrastination, I have written a productivity-oriented application in order to avoid actual productivity. The app is called InSTiL, and its goal is to make it easy to log how much time you spend working on various projects. The source is available on Github, and the Readme provides a succinct overview of InSTiL&amp;rsquo;s functionality.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmick0/instil&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/instil&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    
    
    <item>
      <title>A C&#43;&#43; encapsulation of the Linux inotify API</title>
      <link>https://lo.calho.st/posts/cpp-inotify/</link>
      <pubDate>Sun, 01 Feb 2015 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/cpp-inotify/</guid>
      <description>&lt;p&gt;The inotify API allows you to monitor a file or directory for various events such as file creation, modification, and deletion. It is part of the Linux kernel and the glibc userspace library, however its C API can be cumbersome to use in a C++ application. A &lt;a href=&#34;http://inotify.aiken.cz/?section=inotify-cxx&amp;amp;page=doc&amp;amp;lang=en&#34; target=&#34;_blank&#34; &gt;C++ binding of inotify does exist&lt;/a&gt;, but it still requires the application developer to write an unsightly wait-and-handle loop. My goal for this project was to create an asynchronous event-driven API through which filesystem events can be processed.&lt;/p&gt;
&lt;p&gt;My inotify encapsulation, &lt;a href=&#34;https://github.com/tmick0/Inotify_cpp&#34; target=&#34;_blank&#34; &gt;Inotify_cpp&lt;/a&gt;, hides inotify&amp;rsquo;s read loop behind a simple system of event callbacks. A programmer who seeks to monitor the filesystem only needs to write an event callback (which can be as simple or as complicated as desired) and attach it to a watch. It will then receive events as they occur, all while allowing the main application thread to attend to more important matters. An &lt;a href=&#34;https://github.com/tmick0/Inotify_cpp/blob/master/app/main.cpp&#34; target=&#34;_blank&#34; &gt;example of the library&amp;rsquo;s ease of use&lt;/a&gt; is included.&lt;/p&gt;
&lt;p&gt;Currently, Inotify_cpp implements all the functionality of the underlying inotify API, only with the exception of cookie handling for move events. However, this will be implemented soon, and I even plan to extend my library beyond the capabilities of traditional inotify. Most importantly, I will add support for recursive watching &amp;ndash; one of the most sought-after features inotify lacks.&lt;/p&gt;
&lt;p&gt;Inotify_cpp is hosted on Github. You can clone or fork it here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmick0/Inotify_cpp&#34; target=&#34;_blank&#34; &gt;https://github.com/tmick0/Inotify_cpp&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>RIOT OS ported to TI Tiva C Connected Launchpad</title>
      <link>https://lo.calho.st/posts/riot-os-for-ti-tiva-c-launchpad/</link>
      <pubDate>Sat, 31 Jan 2015 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/riot-os-for-ti-tiva-c-launchpad/</guid>
      <description>&lt;p&gt;My current project is porting &lt;a href=&#34;https://github.com/RIOT-OS/RIOT&#34; target=&#34;_blank&#34; &gt;RIOT OS&lt;/a&gt; to the &lt;a href=&#34;http://www.ti.com/tool/ek-tm4c1294xl&#34; target=&#34;_blank&#34; &gt;EK-TM4C1294XL&lt;/a&gt; evaluation board. RIOT is an embedded operating system aimed at the Internet of Things, developed primarily by Free University of Berlin. The EK-TM4C1294XL is a pretty powerful board, featuring an ARM Cortex M4 MCU and built-in Ethernet MAC. So far, I have implemented only the most basic support for the CPU - just timers and UART. However, I&amp;rsquo;m currently working on the Ethernet drivers (almost done) and my next focus will be drivers for an XBee add-in.&lt;/p&gt;
&lt;p&gt;This port of RIOT (and the boards) will be deployed in a Smart Grid project being worked on by myself and the others in the Network Systems and Optimization Lab at NMSU.&lt;/p&gt;
&lt;p&gt;The port is forked from the 2014.12 release of RIOT.&lt;/p&gt;
&lt;p&gt;My repository is here: &lt;a href=&#34;https://github.com/nsol-nmsu/RIOT&#34; target=&#34;_blank&#34; &gt;https://github.com/nsol-nmsu/RIOT&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Roll your own dynamic DNS (Ubuntu)</title>
      <link>https://lo.calho.st/posts/dynamic-dns-ubuntu/</link>
      <pubDate>Thu, 24 Jul 2014 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/dynamic-dns-ubuntu/</guid>
      <description>&lt;p&gt;Dynamic DNS, or DDNS, is a type of DNS configuration which allows hosts with dynamic IP addresses to automatically update their DNS records. Often users will rely on services such as DynDNS or No-IP to manage this type of setup, but it is actually relatively easy to run your own DDNS server. Of course, this requires that you have your own domain name and access to at least one host with a static IP (to use as the DNS server).&lt;/p&gt;
&lt;h2 id=&#34;step-1-set-up-dns-records-for-your-dynamic-subdomain&#34;&gt;Step 1: Set up DNS records for your dynamic subdomain&lt;/h2&gt;
&lt;p&gt;I do not recommend hosting all of your DNS records yourself. I manage most of my records on Cloudflare, but delegate my dynamic subdomain to my own nameserver. In this step, you&amp;rsquo;ll need to decide two things: what subdomain you want your dynamic hosts to live on, and what server you will host their DNS records on.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s assume you&amp;rsquo;ll use ddns.example.com as your dynamic subdomain, and ns1.example.com as the nameserver. With this setup you will need to add a NS record delegating ddns.example.com to ns1.example.com. Then your dynamic host will be named, for example, host1.ddns.example.com. However, you can also add a CNAME pointing ddns.example.com to host1.ddns.example.com, allowing you to use a shorter name to talk to the machine.&lt;/p&gt;
&lt;h2 id=&#34;step-2-install-and-configure-your-nameserver&#34;&gt;Step 2: Install and configure your nameserver&lt;/h2&gt;
&lt;p&gt;Now, on ns1.example.com, install the bind9 package (as it is named on Ubuntu). Edit /etc/bind/named.conf.local and add the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;zone &amp;#34;ddns.example.com&amp;#34; {
  type master;
  file &amp;#34;/var/lib/bind/ddns.example.com&amp;#34;;
  update-policy {
    grant *.ddns.example.com self ddns.example.com.;
  };
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This just allows your the hosts on your ddns subdomain to update their own records (after they authenticate themselves). Now to generate the key for authentication:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ddns-confgen -r /dev/urandom -q -a hmac-md5 -k host1.ddns.example.com &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  -s host1.ddns.example.com. | tee -a /etc/bind/ddns.example.com.keys   &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;   &amp;gt; /etc/bind/key.host1.ddns.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This can be repeated for however many hosts you need on the subdomain. Then copy the key.*.ddns.example.com files to their respective hosts, and delete them off the nameserver. Now, we have to make sure to set secure permissions on the nameserver&amp;rsquo;s copy of the keys:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ chown root:bind /etc/bind/ddns.example.com.keys
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ chmod u&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;rw,g&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;r,o&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; /etc/bind/ddns.example.com.keys
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, add a line to /etc/bind/named.conf.local to use the keys you just created&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;include &amp;#34;/etc/bind/ddns.example.com.keys&amp;#34;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The server is now configured, but we still have to create a zonefile which will store the DNS records.&lt;/p&gt;
&lt;h2 id=&#34;step-3-create-the-zonefile&#34;&gt;Step 3: Create the zonefile&lt;/h2&gt;
&lt;p&gt;Create /var/lib/bind/ddns.example.com and add the following information:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ORIGIN .
$TTL 300             ; 5 minutes
ddns.example.com IN SOA  ns1.example.com. root.example.com. (
          1          ; serial
          3600       ; refresh (1 hour)
          600        ; retry (10 minutes)
          604800     ; expire (1 week)
          300        ; minimum (5 minutes)
          )
NS      ns1.example.com.
$ORIGIN ddns.example.com.
$TTL 60 ; 1 minute
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s it. Now start up bind and we&amp;rsquo;ll move to the dynamic host to configure it.&lt;/p&gt;
&lt;h2 id=&#34;step-4-configure-the-dynamic-host&#34;&gt;Step 4: Configure the dynamic host&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s try to manually write a record to server. Write a simple text file with this content:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;server ns1.example.com
zone ddns.example.com.
update delete host1.ddns.example.com.
update add host1.ddns.example.com. 60 A 192.168.0.1
update add host1.ddns.example.com. 60 TXT &amp;#34;Updated on $(date)&amp;#34;
send
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, pipe it into nsupdate:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ nsupdate -k /path/to/key.host1.ddns.example.com &amp;lt; file_you_just_wrote.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it worked, then everything is set up correctly! However, it would be nice to have the IP update automatically, right? Fortunately, there&amp;rsquo;s a script that can do that! It just queries OpenDNS for your IP address, then sends the update to your nameserver. Just set up a cronjob (for example, every 15 minutes) to run it, and your DDNS will be completely automated. &lt;em&gt;(Sorry, the site I was hosting the script on was no longer active, otherwise there would be a link right here.)&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    
    
    
    <item>
      <title>Quick and easy OpenVPN server config (Ubuntu)</title>
      <link>https://lo.calho.st/posts/openvpn-config-ubuntu/</link>
      <pubDate>Mon, 21 Jul 2014 00:00:00 +0000</pubDate>
      
      <guid>https://lo.calho.st/posts/openvpn-config-ubuntu/</guid>
      <description>&lt;p&gt;VPNs allow you to route your Internet traffic through an encrypted tunnel to a remote server, enhancing your privacy while online. Often a VPS is around the same price per month as a dedicated VPN service, but gives you much more freedom and utility as a poweruser. This post will overview a basic routed OpenVPN configuration on an Ubuntu machine.&lt;/p&gt;
&lt;h2 id=&#34;step-1-drivers-and-packages&#34;&gt;Step 1: Drivers and packages&lt;/h2&gt;
&lt;p&gt;First you need to enable TUN/TAP on your VPS. Usually your host will give you an option for this in your Control Panel. If not, you may have to file a support ticket. We will need TUN to emulate a new network device for the VPN to use. To check whether you have TUN enabled, do &lt;code&gt;sudo cat /dev/net/tun&lt;/code&gt;. If you get the message &amp;ldquo;File descriptor in bad state,&amp;rdquo; then TUN is working. If you get &amp;ldquo;No such device,&amp;rdquo; then the TUN driver is missing.&lt;/p&gt;
&lt;p&gt;Now we will need to install OpenVPN, as well as Easy-RSA which we will use for our PKI:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo apt-get install openvpn easy-rsa
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-2-pki-and-keys&#34;&gt;Step 2: PKI and keys&lt;/h2&gt;
&lt;p&gt;Set up the PKI environment and generate a CA certificate:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo -s &lt;span style=&#34;color:#75715e&#34;&gt;# become root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir /etc/openvpn/easy-rsa/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cp -r /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ editor /etc/openvpn/easy-rsa/vars &lt;span style=&#34;color:#75715e&#34;&gt;# set up parameters for your CA&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd /etc/openvpn/easy-rsa/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ source vars
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./clean-all
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./build-ca &lt;span style=&#34;color:#75715e&#34;&gt;# the CA certificate is now built&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Build a key and certificate for the server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./build-key-server SERVER_NAME_HERE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./build-dh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd keys/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cp SERVER_NAME_HERE.crt SERVER_NAME_HERE.key ca.crt dh1024.pem /etc/openvpn/ 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Make keys and certificates for each client, and transfer them to the remote hosts (for example using SCP):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./build-key CLIENT_NAME
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ scp /etc/openvpn/ca.crt CLIENT_MACHINE: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ scp /etc/openvpn/easy-rsa/keys/CLIENT_NAME.key CLIENT_MACHINE:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ scp /etc/openvpn/easy-rsa/keys/CLIENT_NAME.crt CLIENT_MACHINE:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-3-server-config&#34;&gt;Step 3: Server config&lt;/h2&gt;
&lt;p&gt;Copy the default the server configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz /etc/openvpn/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ gzip -d /etc/openvpn/server.conf.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now edit /etc/openvpn/server.conf and set the correct filenames for the server certificates and keys:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ca ca.crt
cert SERVER_NAME.crt
key SERVER_NAME.key 
dh dh1024.pem
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;step-4-iptables&#34;&gt;Step 4: iptables&lt;/h2&gt;
&lt;p&gt;You may have to set up some routes and firewall rules to get your VPN to work as expected (using NAT):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -A FORWARD -o tun0 -j ACCEPT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -A FORWARD -s 10.8.0.0/24 -j ACCEPT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -A FORWARD -j REJECT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o venet0:0 -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ /sbin/iptables -t nat -A POSTROUTING -j SNAT --to-source YOUR_SERVER_IP_HERE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re using an iptables manager, you can easily add these rules to that service&amp;rsquo;s configuration. You can also put them in a new initscript, in an on-boot cronjob, or some other script that gets run when the server boots.&lt;/p&gt;
&lt;h2 id=&#34;step-5-start-the-daemon-and-connect-clients&#34;&gt;Step 5: Start the daemon and connect clients&lt;/h2&gt;
&lt;p&gt;Now just start OpenVPN:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ service openvpn start
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then configure your client with the keys you scp&amp;rsquo;d. Everything should be peachy.&lt;/p&gt;</description>
    </item>
    
    
    
    
  </channel>
</rss>
